use derive_setters::Setters;
use jiff::Timestamp;
use serde::{Deserialize, Serialize};
use serde_with::serde_as;
use crate::{backend::node::ExtendedAttribute, repofile::Node};
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum TimeOption {
Yes,
Mtime,
No,
}
impl TimeOption {
pub fn map_or_else(
self,
default: impl FnOnce() -> Option<Timestamp>,
mtime: Option<Timestamp>,
) -> Option<Timestamp> {
match self {
Self::Yes => default(),
Self::Mtime => mtime,
Self::No => None,
}
}
}
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum DevIdOption {
Yes,
#[default]
Hardlink,
No,
}
impl DevIdOption {
pub fn map_or_else(self, dev_id: impl FnOnce() -> u64, hardlink: bool) -> u64 {
match self {
Self::Yes => dev_id(),
Self::Hardlink if hardlink => dev_id(),
_ => 0,
}
}
}
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, Default, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum BlockdevOption {
#[default]
Special,
File,
}
#[cfg_attr(feature = "clap", derive(clap::ValueEnum))]
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "kebab-case")]
pub enum XattrOption {
#[default]
Yes,
No,
}
impl XattrOption {
pub fn map_or_else(
self,
default: impl FnOnce() -> Vec<ExtendedAttribute>,
) -> Vec<ExtendedAttribute> {
match self {
Self::Yes => default(),
Self::No => Vec::new(),
}
}
}
#[serde_as]
#[cfg_attr(feature = "clap", derive(clap::Parser))]
#[cfg_attr(feature = "merge", derive(conflate::Merge))]
#[derive(Deserialize, Serialize, Default, Clone, Debug, PartialEq, Eq, Setters)]
#[serde(default, rename_all = "kebab-case", deny_unknown_fields)]
#[setters(into)]
#[non_exhaustive]
#[allow(clippy::struct_field_names)]
pub struct NodeModification {
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_atime: Option<TimeOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_ctime: Option<TimeOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_devid: Option<DevIdOption>,
#[cfg_attr(feature = "clap", clap(long))]
#[cfg_attr(feature = "merge", merge(strategy = conflate::option::overwrite_none))]
pub set_xattrs: Option<XattrOption>,
}
impl NodeModification {
#[must_use]
pub fn is_empty(&self) -> bool {
self == &Self::default()
}
#[must_use]
pub fn modify_node(&self, node: &mut Node) -> bool {
let mut meta = node.meta.clone();
let mtime = meta.mtime;
meta.atime = self
.set_atime
.unwrap_or(TimeOption::Mtime)
.map_or_else(|| meta.atime, mtime);
meta.ctime = self
.set_ctime
.unwrap_or(TimeOption::Yes)
.map_or_else(|| meta.ctime, mtime);
meta.device_id = self
.set_devid
.unwrap_or_default()
.map_or_else(|| meta.device_id, meta.links > 1 && !node.is_dir());
meta.extended_attributes = self
.set_xattrs
.unwrap_or_default()
.map_or_else(|| meta.extended_attributes);
let changed = node.meta != meta;
node.meta = meta;
changed
}
}