use crate::client::{Client, Slot};
use crate::error::{Error, Result};
use crate::events::lock;
use crate::wire::{self, method};
use std::io;
use std::sync::{Arc, Condvar, Mutex};
use std::time::{Duration, Instant};
pub const MAX_OP_FAILURES: usize = 64;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum Preserve {
Times,
Xattr,
Acl,
}
impl Preserve {
fn to_wire(self) -> wire::PreserveKind {
match self {
Preserve::Times => wire::PreserveKind::Times,
Preserve::Xattr => wire::PreserveKind::Xattr,
Preserve::Acl => wire::PreserveKind::Acl,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct CloneOptions {
pub overwrite: bool,
pub preserve: Vec<Preserve>,
}
impl CloneOptions {
pub fn with_overwrite(mut self, overwrite: bool) -> CloneOptions {
self.overwrite = overwrite;
self
}
pub fn with_preserve(mut self, preserve: Vec<Preserve>) -> CloneOptions {
self.preserve = preserve;
self
}
fn to_wire(&self) -> wire::CloneOptions {
wire::CloneOptions {
overwrite: self.overwrite,
preserve: self.preserve.iter().map(|p| p.to_wire() as i32).collect(),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum CloneOutcome {
Cloned,
Overwritten,
Skipped,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
#[non_exhaustive]
pub struct OpCounts {
pub cloned: u64,
pub overwritten: u64,
pub skipped: u64,
pub failed: u64,
pub removed: u64,
pub protected: u64,
}
impl OpCounts {
pub(crate) fn from_notice(m: &wire::Notice) -> OpCounts {
OpCounts {
cloned: m.cloned,
overwritten: m.overwritten,
skipped: m.skipped,
failed: m.failed,
removed: m.removed,
protected: m.protected,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct OpFailure {
pub path: String,
pub code: String,
pub message: String,
}
impl OpFailure {
pub(crate) fn new(path: String, code: String, message: String) -> OpFailure {
OpFailure { path, code, message }
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub struct OpResult {
pub counts: OpCounts,
pub failures: Vec<OpFailure>,
}
struct Inner {
failures: Vec<OpFailure>,
end: Option<std::result::Result<OpCounts, Error>>,
}
pub(crate) struct OpState {
inner: Mutex<Inner>,
cv: Condvar,
}
impl OpState {
pub(crate) fn new() -> Arc<OpState> {
Arc::new(OpState {
inner: Mutex::new(Inner {
failures: Vec::new(),
end: None,
}),
cv: Condvar::new(),
})
}
pub(crate) fn add_failure(&self, f: OpFailure) {
let mut g = lock(&self.inner);
if g.end.is_none() && g.failures.len() < MAX_OP_FAILURES {
g.failures.push(f);
}
}
pub(crate) fn finish(&self, counts: OpCounts) {
self.settle(Ok(counts));
}
pub(crate) fn fail(&self, e: Error) {
self.settle(Err(e));
}
fn settle(&self, end: std::result::Result<OpCounts, Error>) {
let mut g = lock(&self.inner);
if g.end.is_none() {
g.end = Some(end);
drop(g);
self.cv.notify_all();
}
}
fn wait(&self, timeout: Duration) -> Result<OpResult> {
let deadline = Instant::now().checked_add(timeout);
let mut g = lock(&self.inner);
loop {
match &g.end {
Some(Ok(counts)) => {
return Ok(OpResult {
counts: *counts,
failures: g.failures.clone(),
})
}
Some(Err(e)) => return Err(e.dup()),
None => {}
}
let left = match deadline {
Some(d) => d.saturating_duration_since(Instant::now()),
None => Duration::from_secs(3600),
};
if left.is_zero() {
return Err(Error::Io(io::Error::new(
io::ErrorKind::TimedOut,
"operation wait timed out",
)));
}
g = self.cv.wait_timeout(g, left).unwrap_or_else(|e| e.into_inner()).0;
}
}
}
pub struct Operation {
id: u64,
slot: Arc<Slot>,
epoch: u64,
state: Arc<OpState>,
}
impl Operation {
pub fn id(&self) -> u64 {
self.id
}
pub fn wait(&self, timeout: Duration) -> Result<OpResult> {
self.state.wait(timeout)
}
pub fn cancel(&self) -> Result<()> {
let req = wire::OpCancelRequest { op_id: self.id };
self
.slot
.run(Some(self.epoch), |c| {
c.call_msg::<wire::Empty>(method::OP_CANCEL, &req, &[])
})
.map(drop)
}
}
impl Client {
pub fn clone_entry(&self, from: &str, to: &str, opts: &CloneOptions) -> Result<CloneOutcome> {
let req = wire::CloneRequest {
from: from.into(),
to: to.into(),
options: Some(opts.to_wire()),
};
let r: wire::CloneResponse = self.unary(method::CLONE, &req)?;
match wire::CloneOutcome::try_from(r.outcome) {
Ok(wire::CloneOutcome::Cloned) => Ok(CloneOutcome::Cloned),
Ok(wire::CloneOutcome::Overwritten) => Ok(CloneOutcome::Overwritten),
Ok(wire::CloneOutcome::Skipped) => Ok(CloneOutcome::Skipped),
Err(_) => Err(Error::Protocol(format!("unknown clone outcome {}", r.outcome))),
}
}
pub fn clone_dir(&self, from: &str, to: &str, opts: &CloneOptions) -> Result<Operation> {
let req = wire::CloneDirRequest {
from: from.into(),
to: to.into(),
options: Some(opts.to_wire()),
};
self.start_op(|c, st| c.call_op::<wire::CloneDirResponse>(method::CLONE_DIR, &req, st, |r| r.op_id))
}
pub fn remove_dir(&self, path: &str) -> Result<Operation> {
let req = wire::RemoveDirRequest { path: path.into() };
self.start_op(|c, st| c.call_op::<wire::RemoveDirResponse>(method::REMOVE_DIR, &req, st, |r| r.op_id))
}
fn start_op(&self, f: impl FnOnce(&crate::client::Sock, Arc<OpState>) -> Result<u64>) -> Result<Operation> {
let state = OpState::new();
let st = state.clone();
let (id, slot, epoch) = self.run_any(move |c| f(c, st))?;
Ok(Operation {
id,
slot: slot.clone(),
epoch,
state,
})
}
}