106 lines
3.1 KiB
Rust
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(())
|
|
}
|