use crate::*;
use alux_ext::ext;
use ambassador::delegatable_trait;
use rsynko_manager::ChangeKind;
#[delegatable_trait]
pub trait SyncChangeAlg: RsyncSorts {
fn sync_change(
&self,
path: impl Into<String>,
kind: ChangeKind,
size: Option<u64>,
) -> Self::Change;
}
#[delegatable_trait]
pub trait SyncObservationAlg: RsyncSorts {
fn observed_change(&self, change: Self::Change) -> Self::Observation;
fn observed_progress(&self, transferred: u64, percent: u16) -> Self::Observation;
fn observed_nothing(&self) -> Self::Observation;
}
#[delegatable_trait]
pub trait SyncObservationViewAlg: RsyncSorts {
fn observation_change<'a>(
&self,
observation: &'a Self::Observation,
) -> Option<&'a Self::Change>;
fn observation_progress(&self, observation: &Self::Observation) -> Option<(u64, u16)>;
}
pub const DELETION_MARK: &str = "*deleting";
pub const FIELD_MARK: char = '|';
#[ext(name = SyncReadExt)]
pub impl<This> This
where
This: SyncChangeAlg + SyncObservationAlg,
{
fn read_sync_line(&self, line: &str) -> This::Observation {
let line = line.trim_end();
if let Some(change) = self.read_sync_change(line) {
return self.observed_change(change);
}
read_advance(line).map_or_else(
|| self.observed_nothing(),
|(transferred, percent)| self.observed_progress(transferred, percent),
)
}
fn read_sync_change(&self, line: &str) -> Option<This::Change> {
let mut fields = line.splitn(3, FIELD_MARK);
let flags = fields.next()?.trim();
let size = fields.next()?.trim();
let path = fields.next()?.trim();
if path.is_empty() || touches_folder(flags) {
return None;
}
let kind = read_kind(flags)?;
let size = (kind != ChangeKind::Delete)
.then(|| read_count(size))
.flatten();
Some(self.sync_change(path, kind, size))
}
}
fn touches_folder(flags: &str) -> bool {
let mut marks = flags.chars();
let happening = marks.next();
let kind_of_path = marks.next();
matches!(kind_of_path, Some('d'))
&& matches!(happening, Some('.' | 'c' | '>' | '<'))
&& !marks.as_str().starts_with("+++")
}
fn read_kind(flags: &str) -> Option<ChangeKind> {
if flags.starts_with(DELETION_MARK) {
return Some(ChangeKind::Delete);
}
let mut marks = flags.chars();
let happening = marks.next()?;
if !matches!(happening, '<' | '>' | 'c' | 'h' | '.') {
return None;
}
let _kind_of_path = marks.next()?;
let attributes = marks.as_str();
if attributes.starts_with("+++") {
return Some(ChangeKind::Create);
}
if happening == '.' && attributes.chars().all(|mark| mark == '.' || mark == ' ') {
return Some(ChangeKind::Unchanged);
}
Some(ChangeKind::Update)
}
fn read_advance(line: &str) -> Option<(u64, u16)> {
let mut fields = line.split_whitespace();
let transferred = read_count(fields.next()?)?;
let percent = fields.next()?.strip_suffix('%')?.parse::<u16>().ok()?;
Some((transferred, percent.min(100)))
}
fn read_count(text: &str) -> Option<u64> {
let digits = text.replace([',', '.', '\u{a0}'], "");
(!digits.is_empty() && digits.chars().all(|digit| digit.is_ascii_digit()))
.then(|| digits.parse().ok())
.flatten()
}