use crate::*;
use alux_ext::ext;
use alux_sdk::case_mapping;
use ambassador::delegatable_trait;
#[delegatable_trait]
pub trait SyncCommandAlg: RsyncSorts {
fn sync_command(&self, program: impl Into<String>, arguments: impl IntoIterator<Item = String>) -> Self::Command;
}
#[delegatable_trait]
pub trait SyncCommandViewAlg: RsyncSorts {
fn command_program<'a>(&self, command: &'a Self::Command) -> &'a str;
fn command_arguments<'a>(&self, command: &'a Self::Command) -> impl Iterator<Item = &'a str>;
}
pub const SYNC_PROGRAM: &str = "rsync";
pub const SYNC_OUT_FORMAT: &str = "--out-format=%i|%l|%n";
pub const SYNC_REHEARSAL: &str = "--dry-run";
pub const SYNC_MIRROR: &str = "--delete";
#[ext(name = SyncCommandExt)]
pub impl<This> This
where
This: SyncCommandAlg + SyncCommandViewAlg + RsyncEndpointAlg + RsyncEndpointViewAlg,
{
fn transfer_command(&self, source: &This::Endpoint, destination: &This::Endpoint, mode: SyncMode) -> This::Command {
let arguments = [
"--archive",
"--partial",
"--itemize-changes",
"--itemize-changes",
SYNC_OUT_FORMAT,
"--info=progress2",
]
.into_iter()
.map(str::to_owned)
.chain(mode.profile.profile_arguments().iter().map(|argument| (*argument).to_owned()))
.chain(mode.rehearsal.then(|| SYNC_REHEARSAL.to_owned()))
.chain([self.endpoint_text(source), self.endpoint_text(destination)]);
self.sync_command(SYNC_PROGRAM, arguments)
}
fn command_rehearses(&self, command: &This::Command) -> bool {
self.command_arguments(command).any(|argument| argument == SYNC_REHEARSAL)
}
fn command_mirrors(&self, command: &This::Command) -> bool {
self.command_arguments(command).any(|argument| argument == SYNC_MIRROR)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct SyncMode {
pub profile: SyncProfile,
pub rehearsal: bool,
}
impl SyncMode {
#[must_use]
pub const fn rehearsal() -> Self {
Self { profile: SyncProfile::Copy, rehearsal: true }
}
#[must_use]
pub const fn transfer() -> Self {
Self { profile: SyncProfile::Copy, rehearsal: false }
}
#[must_use]
pub const fn rehearsed(mut self, rehearsal: bool) -> Self {
self.rehearsal = rehearsal;
self
}
#[must_use]
pub const fn profiled(mut self, profile: SyncProfile) -> Self {
self.profile = profile;
self
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum SyncProfile {
Copy,
Mirror,
MirrorKeeping,
MirrorWhole,
MirrorReadable,
SkipNewer,
CompareContent,
Resume,
Move,
LimitRate,
KeepMarks,
OneDisk,
SkipJunk,
}
case_mapping! {
SyncProfile, &'static str as &str,
Copy <=> "copy",
Mirror <=> "mirror",
MirrorKeeping <=> "mirror-keeping",
MirrorWhole <=> "mirror-whole",
MirrorReadable <=> "mirror-readable",
SkipNewer <=> "skip-newer",
CompareContent <=> "compare-content",
Resume <=> "resume",
Move <=> "move",
LimitRate <=> "limit-rate",
KeepMarks <=> "keep-marks",
OneDisk <=> "one-disk",
SkipJunk <=> "skip-junk",
}
impl SyncProfile {
#[must_use]
pub const fn summary(self) -> &'static str {
match self {
Self::Copy => "adds and replaces, removes nothing, good for topping a folder up",
Self::Mirror => "makes the destination match exactly, good for keeping a copy current",
Self::MirrorKeeping => "mirrors, keeps what it replaces, compresses, good for remote backups",
Self::MirrorWhole => "mirrors by sending whole files, good for local disks and SSDs",
Self::MirrorReadable => "mirrors and makes everything readable, good for publishing a web root",
Self::SkipNewer => "replaces nothing newer at the destination, good for merging two copies",
Self::CompareContent => "compares what files hold, not when they changed, good after clock drift",
Self::Resume => "continues large files where they left off, good for flaky links",
Self::Move => "moves, removing what arrived safely, good for clearing a staging area",
Self::LimitRate => "leaves room on the line, good for a shared connection",
Self::KeepMarks => "keeps links, ownership, and every file mark, good for system backups",
Self::OneDisk => "stays on the disk it started on, good for roots with mounts under",
Self::SkipJunk => "leaves behind what no one meant to keep, good for editor droppings",
}
}
#[must_use]
pub const fn profile_arguments(self) -> &'static [&'static str] {
match self {
Self::Copy => &[],
Self::Mirror => &["--delete"],
Self::MirrorKeeping => &["--compress", "--delete", "--backup"],
Self::MirrorWhole => &["--whole-file", "--delete"],
Self::MirrorReadable => &["--delete", "--chmod=D755,F644"],
Self::SkipNewer => &["--update"],
Self::CompareContent => &["--checksum"],
Self::Resume => &["--inplace", "--append-verify"],
Self::Move => &["--remove-source-files"],
Self::LimitRate => &["--bwlimit=2000"],
Self::KeepMarks => &["--hard-links", "--acls", "--xattrs"],
Self::OneDisk => &["--one-file-system"],
Self::SkipJunk => &["--exclude=.DS_Store", "--exclude=Thumbs.db", "--exclude=*.tmp"],
}
}
}