Files
packie/src/link.rs
T
2026-08-09 21:16:48 +08:00

106 lines
3.1 KiB
Rust

use crate::{Packie, package::PkgIdent};
use serde::{Deserialize, Serialize};
use std::{
fs::File,
io::{BufRead, BufReader},
path::{Path, PathBuf},
};
impl super::Packie {
pub fn links_of_pkg(&self, pkgident: &PkgIdent) -> LinksFile {
LinksFile(self.local_data.links_file(pkgident))
}
}
/// 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)]
pub struct LinkDescription {
/// Path of the link source, relative to the package bundle.
pub from: String,
/// Path of the link destination, must be absolute.
pub to: String,
/// True if the link is enabled on installation, by default.
#[serde(default)]
pub default: bool,
}
pub fn activate_by_description(
packie: &mut Packie,
pkg_ident: &PkgIdent,
description: &LinkDescription,
) -> std::io::Result<()> {
let bundle_path = packie.profile.pkg_dir.join(pkg_ident.to_string());
let from = bundle_path.join(&description.from);
create(from, &description.to)
}
pub fn deactivate_by_description(
packie: &mut Packie,
pkg_ident: &PkgIdent,
description: &LinkDescription,
) -> std::io::Result<()> {
let bundle_path = packie.profile.pkg_dir.join(pkg_ident.to_string());
let from = bundle_path.join(&description.from);
let link_from = std::fs::read_link(&description.to)?;
if link_from != from {
return Err(std::io::ErrorKind::InvalidData.into());
}
remove(&description.to)
}
pub fn create(from: impl AsRef<Path>, to: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(
to.as_ref()
.parent()
.ok_or_else(|| std::io::ErrorKind::InvalidInput)?,
)?;
crate::common::symlink(from.as_ref(), to.as_ref())?;
Ok(())
}
/// Tries to remove a link at a given path. Also removes all its parent directories if they become empty after
/// this deletion.
///
/// Note that this is not an atomic operation. Take care of TOCTOU and race condition.
///
/// # Errors
/// It would return an standard library IO error in given error kind:
///
/// - `NotFound`: The file could not be located.
/// - `InvalidData`: The file is not a link.
pub fn remove(path: impl Into<PathBuf>) -> std::io::Result<()> {
let mut path = path.into();
if !path.exists() {
return Err(std::io::ErrorKind::NotFound.into());
}
if std::fs::read_link(&path).is_err() {
return Err(std::io::ErrorKind::InvalidData.into());
}
std::fs::remove_file(&path)?;
while path.pop() {
_ = std::fs::remove_dir(&path);
}
Ok(())
}