feat: introduce new links format
Signed-off-by: sisungo <[email protected]>
This commit is contained in:
+18
-3
@@ -1,7 +1,10 @@
|
||||
use anyhow::anyhow;
|
||||
use clap::Parser;
|
||||
use indicatif::ProgressBar;
|
||||
use packie::repo::RepoServeDir;
|
||||
use packie::{
|
||||
package::{Package, PkgIdent},
|
||||
repo::RepoServeDir,
|
||||
};
|
||||
use std::path::PathBuf;
|
||||
|
||||
#[derive(Debug, Parser)]
|
||||
@@ -13,6 +16,10 @@ pub struct Cli {
|
||||
#[arg(long, short)]
|
||||
add_package: Option<Vec<PathBuf>>,
|
||||
|
||||
/// Remove package(s) from the repository
|
||||
#[arg(long, short)]
|
||||
remove_package: Option<Vec<PkgIdent>>,
|
||||
|
||||
/// Clone or pull remote repository
|
||||
#[arg(long, short)]
|
||||
clone: Option<String>,
|
||||
@@ -34,8 +41,16 @@ pub fn main(cli: Cli) -> anyhow::Result<()> {
|
||||
|
||||
let mut repo = RepoServeDir::open(cli.repo)?;
|
||||
|
||||
for add_package in cli.add_package.into_iter().flatten() {
|
||||
repo.add_package(add_package).map_err(|x| anyhow!("{x}"))?;
|
||||
for pkg in cli.remove_package.into_iter().flatten() {
|
||||
repo.remove_package(&pkg)
|
||||
.map_err(|err| anyhow!("\"{pkg}\": {err}"))?;
|
||||
}
|
||||
|
||||
for file in cli.add_package.into_iter().flatten() {
|
||||
let pkg_ident = Package::open(&file)?.manifest()?.pkg_ident();
|
||||
_ = repo.remove_package(&pkg_ident);
|
||||
repo.add_package(&file)
|
||||
.map_err(|err| anyhow!("\"{}\": {}", file.display(), err))?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+30
-13
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
local::Installation,
|
||||
local::{InstallFlags, Installation},
|
||||
package::{Package, PackageError, PkgIdent, PkgSpec},
|
||||
repo::{RepoError, RepoPackage},
|
||||
};
|
||||
@@ -98,8 +98,7 @@ impl super::Packie {
|
||||
|
||||
// Check if the package is previously installed
|
||||
if !self
|
||||
.search_installation(&pkg_manifest.pkg_ident().into())
|
||||
.map_err(InstallError::Database)?
|
||||
.search_installation(&pkg_manifest.pkg_ident().into())?
|
||||
.is_empty()
|
||||
{
|
||||
return Err(InstallError::AlreadyInstalled);
|
||||
@@ -124,24 +123,37 @@ impl super::Packie {
|
||||
install_date: crate::common::timestamp_secs(),
|
||||
update_date: crate::common::timestamp_secs(),
|
||||
installed_size,
|
||||
install_flags: InstallFlags::default(),
|
||||
};
|
||||
self.local_db
|
||||
.insert_installation(&installation)
|
||||
.map_err(InstallError::Database)?;
|
||||
self.local_db.insert_installation(&installation)?;
|
||||
|
||||
// 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)?;
|
||||
.insert_abspkg(&i, &installation.pkg_manifest.pkg_ident())?;
|
||||
}
|
||||
|
||||
// Copy links file
|
||||
if let Some(links) = package.links() {
|
||||
std::fs::copy(
|
||||
&links.0,
|
||||
self.local_data
|
||||
.links_file(&installation.pkg_manifest.pkg_ident()),
|
||||
)
|
||||
.map_err(|err| InstallError::Database(Box::new(err)))?;
|
||||
}
|
||||
|
||||
// Enable default links
|
||||
for link in pkg_manifest.links.iter() {
|
||||
if !link.default {
|
||||
continue;
|
||||
if let Some(links) = package.links() {
|
||||
for linkdes in links.iter().ok().into_iter().flatten() {
|
||||
let Ok(linkdes) = linkdes else {
|
||||
continue;
|
||||
};
|
||||
if !linkdes.default {
|
||||
continue;
|
||||
}
|
||||
_ = crate::link::activate_by_description(self, &pkg_manifest.pkg_ident(), &linkdes);
|
||||
}
|
||||
_ = crate::link::activate_by_description(self, &pkg_manifest.pkg_ident(), link);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@@ -172,13 +184,18 @@ pub enum InstallError {
|
||||
Repo(RepoError),
|
||||
|
||||
#[error("{}", t!("install.InstallError.Database", error = .0))]
|
||||
Database(rusqlite::Error),
|
||||
Database(Box<dyn std::error::Error + Send + Sync>),
|
||||
}
|
||||
impl From<PackageError> for InstallError {
|
||||
fn from(value: PackageError) -> Self {
|
||||
Self::Package(value)
|
||||
}
|
||||
}
|
||||
impl From<rusqlite::Error> for InstallError {
|
||||
fn from(value: rusqlite::Error) -> Self {
|
||||
Self::Database(Box::new(value))
|
||||
}
|
||||
}
|
||||
|
||||
fn default_install_pkgspec(pkg_ident: PkgIdent) -> PkgSpec {
|
||||
PkgSpec {
|
||||
|
||||
+4
-1
@@ -10,7 +10,7 @@ pub mod repo;
|
||||
pub mod version;
|
||||
|
||||
use config::AllConfig;
|
||||
use local::{Cache, LocalDb};
|
||||
use local::{Cache, DataDir, LocalDb};
|
||||
use profile::Profile;
|
||||
|
||||
rust_i18n::i18n!("locales/libpackie", fallback = "en");
|
||||
@@ -21,6 +21,7 @@ pub struct Packie {
|
||||
profile: Profile,
|
||||
local_db: LocalDb,
|
||||
config: AllConfig,
|
||||
local_data: DataDir,
|
||||
cache: Cache,
|
||||
}
|
||||
impl Packie {
|
||||
@@ -69,11 +70,13 @@ impl PackieBuilder {
|
||||
let local_db = open_local_db(local_db_path).map_err(BuildPackieError::LocalDb)?;
|
||||
let config = AllConfig::open(&self.profile.packie_config_dir);
|
||||
let cache = Cache(self.profile.packie_cache_dir.clone());
|
||||
let local_data = DataDir(self.profile.packie_data_dir.clone());
|
||||
Ok(Packie {
|
||||
profile: self.profile,
|
||||
local_db,
|
||||
config,
|
||||
cache,
|
||||
local_data,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
+25
-2
@@ -1,6 +1,29 @@
|
||||
use crate::{Packie, package::PkgIdent};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
fs::File,
|
||||
io::{BufRead, BufReader},
|
||||
path::{Path, PathBuf},
|
||||
};
|
||||
|
||||
/// Iterator over link descriptions in a links file.
|
||||
#[derive(Debug)]
|
||||
pub struct LinksFile(pub PathBuf);
|
||||
impl LinksFile {
|
||||
pub const FILENAME: &str = "_links";
|
||||
|
||||
pub fn iter(&self) -> std::io::Result<impl Iterator<Item = std::io::Result<LinkDescription>>> {
|
||||
Ok(BufReader::new(File::open(&self.0)?)
|
||||
.lines()
|
||||
.map(|s| {
|
||||
Ok::<_, std::io::Error>(
|
||||
serde_json::from_str(&s?)
|
||||
.map_err(|err| std::io::Error::new(std::io::ErrorKind::InvalidData, err)),
|
||||
)
|
||||
})
|
||||
.flatten())
|
||||
}
|
||||
}
|
||||
|
||||
/// Description of a link.
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
@@ -52,7 +75,7 @@ pub fn create(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<(
|
||||
|
||||
pub fn remove(path: impl Into<PathBuf>) -> std::io::Result<()> {
|
||||
let mut path = path.into();
|
||||
if !std::fs::metadata(&path)?.is_symlink() {
|
||||
if std::fs::read_link(&path).is_err() {
|
||||
return Err(std::io::ErrorKind::InvalidData.into());
|
||||
}
|
||||
std::fs::remove_file(&path)?;
|
||||
|
||||
+68
-6
@@ -4,7 +4,12 @@ use crate::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use rusqlite::{OpenFlags, Row, params};
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
fmt::Display,
|
||||
path::{Path, PathBuf},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
impl super::Packie {
|
||||
pub fn search_installation(
|
||||
@@ -35,6 +40,29 @@ impl super::Packie {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DataDir(pub PathBuf);
|
||||
impl DataDir {
|
||||
const LINKS_FILES_DIR_NAME: &str = "links_files";
|
||||
|
||||
pub fn links_files_dir(&self) -> PathBuf {
|
||||
self.dir(&[Self::LINKS_FILES_DIR_NAME])
|
||||
}
|
||||
|
||||
pub fn links_file(&self, name: &PkgIdent) -> PathBuf {
|
||||
self.links_files_dir().join(name.to_string())
|
||||
}
|
||||
|
||||
fn dir(&self, relative: &[&str]) -> PathBuf {
|
||||
let mut path = self.0.clone();
|
||||
for i in relative {
|
||||
path.push(i);
|
||||
}
|
||||
_ = std::fs::create_dir_all(&path);
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Cache(pub PathBuf);
|
||||
impl Cache {
|
||||
@@ -73,7 +101,8 @@ CREATE TABLE IF NOT EXISTS "installation"(
|
||||
"install_pkgspec" TEXT NOT NULL,
|
||||
"install_date" INTEGER NOT NULL,
|
||||
"update_date" INTEGER NOT NULL,
|
||||
"installed_size" INTEGER NOT NULL
|
||||
"installed_size" INTEGER NOT NULL,
|
||||
"install_flags" TEXT NOT NULL
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
@@ -100,9 +129,10 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
r#"
|
||||
INSERT INTO "installation"(
|
||||
"pkg_name", "pkg_version", "pkg_arch", "pkg_manifest",
|
||||
"install_pkgspec", "install_date", "update_date", "installed_size"
|
||||
"install_pkgspec", "install_date", "update_date", "installed_size",
|
||||
"install_flags"
|
||||
)
|
||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8)
|
||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||
"#,
|
||||
)?
|
||||
.execute(params![
|
||||
@@ -114,6 +144,7 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
installation.install_date,
|
||||
installation.update_date,
|
||||
installation.installed_size as i64,
|
||||
installation.install_flags.to_string(),
|
||||
])?;
|
||||
Ok(())
|
||||
}
|
||||
@@ -125,7 +156,7 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
) -> rusqlite::Result<()> {
|
||||
self.0
|
||||
.prepare_cached(r#"INSERT INTO "abspkg"("abspkg_ident", "provider") VALUES(?1, ?2)"#)?
|
||||
.execute(params![abspkg.to_string(), provider.to_string(),])?;
|
||||
.execute(params![abspkg.to_string(), provider.to_string()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -176,7 +207,7 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
.map(|x| x > 0)
|
||||
}
|
||||
|
||||
pub fn remove_abspkg_by_provider(&mut self, provider: &PkgIdent) -> rusqlite::Result<bool> {
|
||||
pub fn remove_abspkg_provided_by(&mut self, provider: &PkgIdent) -> rusqlite::Result<bool> {
|
||||
self.0
|
||||
.prepare_cached(r#"DELETE FROM "abspkg" WHERE "provider" = ?1"#)?
|
||||
.execute(params![provider.to_string()])
|
||||
@@ -191,6 +222,36 @@ pub struct Installation {
|
||||
pub install_date: i64,
|
||||
pub update_date: i64,
|
||||
pub installed_size: u64,
|
||||
pub install_flags: InstallFlags,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct InstallFlags(Vec<String>);
|
||||
impl InstallFlags {
|
||||
pub const AUTO_INSTALL: &str = "auto-install";
|
||||
|
||||
pub fn new(v: &[&str]) -> Self {
|
||||
Self(v.iter().map(ToString::to_string).collect())
|
||||
}
|
||||
|
||||
pub fn contains(&self, s: &str) -> bool {
|
||||
self.0.iter().any(|x| x == s)
|
||||
}
|
||||
}
|
||||
impl FromStr for InstallFlags {
|
||||
type Err = Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(s.split(' ').map(Into::into).collect()))
|
||||
}
|
||||
}
|
||||
impl Display for InstallFlags {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
for i in self.0.iter() {
|
||||
write!(f, "{i} ")?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn map_installation(row: &Row) -> rusqlite::Result<Installation> {
|
||||
@@ -204,6 +265,7 @@ fn map_installation(row: &Row) -> rusqlite::Result<Installation> {
|
||||
install_date: row.get("install_date")?,
|
||||
update_date: row.get("update_date")?,
|
||||
installed_size: row.get::<_, i64>("installed_size")? as _,
|
||||
install_flags: row.get::<_, String>("install_flags")?.parse().unwrap(),
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+11
-6
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
link::LinkDescription,
|
||||
link::LinksFile,
|
||||
version::{Version, VersionFilter},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
@@ -39,6 +39,15 @@ impl Package {
|
||||
.map_err(PackageError::Manifest)
|
||||
}
|
||||
|
||||
pub fn links(&self) -> Option<LinksFile> {
|
||||
let path = self.extracted_dir.path().join(LinksFile::FILENAME);
|
||||
if path.exists() {
|
||||
Some(LinksFile(path))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns path of the bundle directory of this package.
|
||||
pub fn bundle_dir(&self) -> PathBuf {
|
||||
self.extracted_dir.path().join("bundle")
|
||||
@@ -86,15 +95,11 @@ pub struct PkgManifest {
|
||||
/// Provided abstract packages.
|
||||
#[serde(default)]
|
||||
pub provides: Vec<AbsPkgIdent>,
|
||||
|
||||
/// Links.
|
||||
#[serde(default)]
|
||||
pub links: Vec<LinkDescription>,
|
||||
}
|
||||
impl PkgManifest {
|
||||
pub const FILENAME: &str = "PkgManifest.json";
|
||||
|
||||
pub const SIZE_MAX: u64 = 64 * 1024;
|
||||
pub const SIZE_MAX: u64 = 2 * 1024 * 1024;
|
||||
|
||||
pub fn read_at<P: AsRef<Path>>(path: P) -> std::io::Result<Self> {
|
||||
let mut data = Vec::with_capacity(1024);
|
||||
|
||||
+17
-4
@@ -1,4 +1,4 @@
|
||||
use crate::package::PkgSpec;
|
||||
use crate::{link::LinksFile, package::PkgSpec};
|
||||
use rust_i18n::t;
|
||||
|
||||
impl super::Packie {
|
||||
@@ -17,8 +17,18 @@ impl super::Packie {
|
||||
let found = found.remove(0);
|
||||
|
||||
// Remove all links
|
||||
for link in found.pkg_manifest.links.iter() {
|
||||
_ = crate::link::deactivate_by_description(self, &found.pkg_manifest.pkg_ident(), link);
|
||||
let links = LinksFile(self.local_data.links_file(&found.pkg_manifest.pkg_ident()));
|
||||
if let Ok(links) = links.iter() {
|
||||
for link in links {
|
||||
let Ok(link) = link else {
|
||||
continue;
|
||||
};
|
||||
_ = crate::link::deactivate_by_description(
|
||||
self,
|
||||
&found.pkg_manifest.pkg_ident(),
|
||||
&link,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Remove package files
|
||||
@@ -33,9 +43,12 @@ impl super::Packie {
|
||||
.remove_installation(&found.pkg_manifest.pkg_ident())
|
||||
.map_err(RemoveError::Database)?;
|
||||
self.local_db
|
||||
.remove_abspkg_by_provider(&found.pkg_manifest.pkg_ident())
|
||||
.remove_abspkg_provided_by(&found.pkg_manifest.pkg_ident())
|
||||
.map_err(RemoveError::Database)?;
|
||||
|
||||
// Remove the links file
|
||||
_ = std::fs::remove_file(self.local_data.links_file(&found.pkg_manifest.pkg_ident()));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,12 +9,14 @@ pub struct Download<P> {
|
||||
from: String,
|
||||
to: PathBuf,
|
||||
on_progress: P,
|
||||
no_existence_check: bool,
|
||||
}
|
||||
impl Download<fn(u64, u64)> {
|
||||
pub fn new(from: String, to: PathBuf) -> Self {
|
||||
Self {
|
||||
from,
|
||||
to,
|
||||
no_existence_check: false,
|
||||
on_progress: |_, _| (),
|
||||
}
|
||||
}
|
||||
@@ -24,13 +26,19 @@ impl<P> Download<P> {
|
||||
Download {
|
||||
from: self.from,
|
||||
to: self.to,
|
||||
no_existence_check: self.no_existence_check,
|
||||
on_progress,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn no_existence_check(mut self) -> Self {
|
||||
self.no_existence_check = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<P: FnMut(u64, u64)> Download<P> {
|
||||
pub fn run(&mut self) -> Result<(), DownloadError> {
|
||||
if self.to.exists() {
|
||||
if self.to.exists() && !self.no_existence_check {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
|
||||
@@ -203,6 +203,22 @@ CREATE TABLE IF NOT EXISTS "abspkg"(
|
||||
.execute(params![abs.to_string(), real.to_string()])?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn delete_package_by_ident(&mut self, ident: &PkgIdent) -> rusqlite::Result<bool> {
|
||||
self.0
|
||||
.prepare_cached(
|
||||
r#"DELETE FROM "package" WHERE "pkg_name" = ?1 AND "pkg_version" = ?2 AND "pkg_arch" = ?3"#,
|
||||
)?
|
||||
.execute(params![ident.name, ident.version.to_string(), ident.arch])
|
||||
.map(|x| x > 0)
|
||||
}
|
||||
|
||||
pub fn delete_abspkg_by_provider(&mut self, ident: &PkgIdent) -> rusqlite::Result<bool> {
|
||||
self.0
|
||||
.prepare_cached(r#"DELETE FROM "abspkg" WHERE "provider" = ?1"#)?
|
||||
.execute(params![ident.to_string()])
|
||||
.map(|x| x > 0)
|
||||
}
|
||||
}
|
||||
|
||||
fn map_repo_package(row: &Row) -> rusqlite::Result<RepoPackage> {
|
||||
@@ -308,6 +324,7 @@ impl<E: FnMut(SyncEvent)> SyncSession<'_, E> {
|
||||
repo_db_url.to_string(),
|
||||
repo_cache_dir.join(RepoDb::FILENAME),
|
||||
)
|
||||
.no_existence_check()
|
||||
.on_progress(Box::new(|sum, total| {
|
||||
self.raise_progress(repo_name.into(), repo_db_url.clone(), sum, total)
|
||||
}))
|
||||
|
||||
+12
-1
@@ -1,6 +1,6 @@
|
||||
use crate::{
|
||||
common::checksum::DEFAULT_HASHER,
|
||||
package::Package,
|
||||
package::{Package, PkgIdent},
|
||||
repo::{RepoDb, RepoPackage, download::Download},
|
||||
};
|
||||
use std::path::{Path, PathBuf};
|
||||
@@ -91,4 +91,15 @@ impl RepoServeDir {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn remove_package(
|
||||
&mut self,
|
||||
ident: &PkgIdent,
|
||||
) -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let path = self.path.join(format!("{ident}.pkg"));
|
||||
std::fs::remove_file(path)?;
|
||||
self.db.delete_package_by_ident(ident)?;
|
||||
self.db.delete_abspkg_by_provider(ident)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user