use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};
use std::{fmt, io};
use distant_core::net::common::ConnectionId;
use distant_core::net::server::Reply;
use distant_core::protocol::{Change, ChangeKindSet, Error, Response};
pub struct RegisteredPath {
id: ConnectionId,
raw_path: PathBuf,
path: PathBuf,
recursive: bool,
allowed: ChangeKindSet,
reply: Box<dyn Reply<Data = Response>>,
}
impl fmt::Debug for RegisteredPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RegisteredPath")
.field("raw_path", &self.raw_path)
.field("path", &self.path)
.field("recursive", &self.recursive)
.field("allowed", &self.allowed)
.finish()
}
}
impl PartialEq for RegisteredPath {
fn eq(&self, other: &Self) -> bool {
self.id == other.id && self.path == other.path && self.allowed == other.allowed
}
}
impl Eq for RegisteredPath {}
impl Hash for RegisteredPath {
fn hash<H: Hasher>(&self, state: &mut H) {
self.id.hash(state);
self.path.hash(state);
self.allowed.hash(state);
}
}
impl RegisteredPath {
pub async fn register(
id: ConnectionId,
path: impl Into<PathBuf>,
recursive: bool,
only: impl Into<ChangeKindSet>,
except: impl Into<ChangeKindSet>,
reply: Box<dyn Reply<Data = Response>>,
) -> io::Result<Self> {
let raw_path = path.into();
let path = tokio::fs::canonicalize(raw_path.as_path()).await?;
let only = only.into();
let except = except.into();
let allowed = if only.is_empty() {
ChangeKindSet::all() - except
} else {
only - except
};
Ok(Self {
id,
raw_path,
path,
recursive,
allowed,
reply,
})
}
pub fn id(&self) -> ConnectionId {
self.id
}
pub fn raw_path(&self) -> &Path {
self.raw_path.as_path()
}
pub fn path(&self) -> &Path {
self.path.as_path()
}
pub fn is_recursive(&self) -> bool {
self.recursive
}
pub fn allowed(&self) -> &ChangeKindSet {
&self.allowed
}
pub fn filter_and_send(&self, change: Change) -> io::Result<bool> {
if !self.allowed().contains(&change.kind) {
return Ok(false);
}
if self.applies_to_path(&change.path) {
self.reply.send(Response::Changed(change)).map(|_| true)
} else {
Ok(false)
}
}
pub fn filter_and_send_error<T>(
&self,
msg: &str,
paths: T,
skip_if_no_paths: bool,
) -> io::Result<bool>
where
T: IntoIterator,
T::Item: AsRef<Path>,
{
let paths: Vec<PathBuf> = paths
.into_iter()
.filter(|p| self.applies_to_path(p.as_ref()))
.map(|p| p.as_ref().to_path_buf())
.collect();
if !paths.is_empty() || !skip_if_no_paths {
self.reply
.send(if paths.is_empty() {
Response::Error(Error::from(msg))
} else {
Response::Error(Error::from(format!("{msg} about {paths:?}")))
})
.map(|_| true)
} else {
Ok(false)
}
}
pub fn applies_to_path(&self, path: &Path) -> bool {
let check_path = |path: &Path| -> bool {
let cnt = path.components().count();
cnt < 2 || self.recursive
};
match (
path.strip_prefix(self.path()),
path.strip_prefix(self.raw_path()),
) {
(Ok(p1), Ok(p2)) => check_path(p1) || check_path(p2),
(Ok(p), Err(_)) => check_path(p),
(Err(_), Ok(p)) => check_path(p),
(Err(_), Err(_)) => false,
}
}
}