mirror of https://github.com/chipsenkbeil/distant
Initial commit
parent
95c0d0c0d1
commit
793844ccbe
@ -1,207 +0,0 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{BitAnd, BitOr, BitXor};
|
||||
use std::str::FromStr;
|
||||
|
||||
use derive_more::{From, Into, IntoIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumMessage, IntoEnumIterator};
|
||||
|
||||
use super::CapabilityKind;
|
||||
|
||||
/// Set of supported capabilities for a server
|
||||
#[derive(Clone, Debug, From, Into, PartialEq, Eq, IntoIterator, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(transparent)]
|
||||
pub struct Capabilities(#[into_iterator(owned, ref)] HashSet<Capability>);
|
||||
|
||||
impl Capabilities {
|
||||
/// Return set of capabilities encompassing all possible capabilities
|
||||
pub fn all() -> Self {
|
||||
Self(CapabilityKind::iter().map(Capability::from).collect())
|
||||
}
|
||||
|
||||
/// Return empty set of capabilities
|
||||
pub fn none() -> Self {
|
||||
Self(HashSet::new())
|
||||
}
|
||||
|
||||
/// Returns true if the capability with described kind is included
|
||||
pub fn contains(&self, kind: impl AsRef<str>) -> bool {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.contains(&cap)
|
||||
}
|
||||
|
||||
/// Adds the specified capability to the set of capabilities
|
||||
///
|
||||
/// * If the set did not have this capability, returns `true`
|
||||
/// * If the set did have this capability, returns `false`
|
||||
pub fn insert(&mut self, cap: impl Into<Capability>) -> bool {
|
||||
self.0.insert(cap.into())
|
||||
}
|
||||
|
||||
/// Removes the capability with the described kind, returning the capability
|
||||
pub fn take(&mut self, kind: impl AsRef<str>) -> Option<Capability> {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.take(&cap)
|
||||
}
|
||||
|
||||
/// Removes the capability with the described kind, returning true if it existed
|
||||
pub fn remove(&mut self, kind: impl AsRef<str>) -> bool {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.remove(&cap)
|
||||
}
|
||||
|
||||
/// Converts into vec of capabilities sorted by kind
|
||||
pub fn into_sorted_vec(self) -> Vec<Capability> {
|
||||
let mut this = self.0.into_iter().collect::<Vec<_>>();
|
||||
|
||||
this.sort_unstable();
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl Capabilities {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(Capabilities)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitand(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitor(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<Capability> for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitor(self, rhs: Capability) -> Self::Output {
|
||||
let mut other = Capabilities::none();
|
||||
other.0.insert(rhs);
|
||||
|
||||
self.bitor(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitxor(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitxor(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Capability> for Capabilities {
|
||||
fn from_iter<I: IntoIterator<Item = Capability>>(iter: I) -> Self {
|
||||
let mut this = Capabilities::none();
|
||||
|
||||
for capability in iter {
|
||||
this.0.insert(capability);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/// Capability tied to a server. A capability is equivalent based on its kind and not description.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub struct Capability {
|
||||
/// Label describing the kind of capability
|
||||
pub kind: String,
|
||||
|
||||
/// Information about the capability
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Will convert the [`Capability`]'s `kind` into a known [`CapabilityKind`] if possible,
|
||||
/// returning None if the capability is unknown
|
||||
pub fn to_capability_kind(&self) -> Option<CapabilityKind> {
|
||||
CapabilityKind::from_str(&self.kind).ok()
|
||||
}
|
||||
|
||||
/// Returns true if the described capability is unknown
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
self.to_capability_kind().is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Capability {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.kind.eq_ignore_ascii_case(&other.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Capability {}
|
||||
|
||||
impl PartialOrd for Capability {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Capability {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.kind
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&other.kind.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Capability {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.to_ascii_lowercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CapabilityKind> for Capability {
|
||||
/// Creates a new capability using the kind's default message
|
||||
fn from(kind: CapabilityKind) -> Self {
|
||||
Self {
|
||||
kind: kind.to_string(),
|
||||
description: kind
|
||||
.get_message()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl Capability {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(Capability)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl CapabilityKind {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(CapabilityKind)
|
||||
}
|
||||
}
|
@ -1,516 +0,0 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{BitOr, Sub};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, DerefMut, IntoIterator};
|
||||
use notify::event::Event as NotifyEvent;
|
||||
use notify::EventKind as NotifyEventKind;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Change to one or more paths on the filesystem
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub struct Change {
|
||||
/// Label describing the kind of change
|
||||
pub kind: ChangeKind,
|
||||
|
||||
/// Paths that were changed
|
||||
pub paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl Change {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(Change)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NotifyEvent> for Change {
|
||||
fn from(x: NotifyEvent) -> Self {
|
||||
Self {
|
||||
kind: x.kind.into(),
|
||||
paths: x.paths,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
strum::Display,
|
||||
EnumString,
|
||||
EnumVariantNames,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ChangeKind {
|
||||
/// Something about a file or directory was accessed, but
|
||||
/// no specific details were known
|
||||
Access,
|
||||
|
||||
/// A file was closed for executing
|
||||
AccessCloseExecute,
|
||||
|
||||
/// A file was closed for reading
|
||||
AccessCloseRead,
|
||||
|
||||
/// A file was closed for writing
|
||||
AccessCloseWrite,
|
||||
|
||||
/// A file was opened for executing
|
||||
AccessOpenExecute,
|
||||
|
||||
/// A file was opened for reading
|
||||
AccessOpenRead,
|
||||
|
||||
/// A file was opened for writing
|
||||
AccessOpenWrite,
|
||||
|
||||
/// A file or directory was read
|
||||
AccessRead,
|
||||
|
||||
/// The access time of a file or directory was changed
|
||||
AccessTime,
|
||||
|
||||
/// A file, directory, or something else was created
|
||||
Create,
|
||||
|
||||
/// The content of a file or directory changed
|
||||
Content,
|
||||
|
||||
/// The data of a file or directory was modified, but
|
||||
/// no specific details were known
|
||||
Data,
|
||||
|
||||
/// The metadata of a file or directory was modified, but
|
||||
/// no specific details were known
|
||||
Metadata,
|
||||
|
||||
/// Something about a file or directory was modified, but
|
||||
/// no specific details were known
|
||||
Modify,
|
||||
|
||||
/// A file, directory, or something else was removed
|
||||
Remove,
|
||||
|
||||
/// A file or directory was renamed, but no specific details were known
|
||||
Rename,
|
||||
|
||||
/// A file or directory was renamed, and the provided paths
|
||||
/// are the source and target in that order (from, to)
|
||||
RenameBoth,
|
||||
|
||||
/// A file or directory was renamed, and the provided path
|
||||
/// is the origin of the rename (before being renamed)
|
||||
RenameFrom,
|
||||
|
||||
/// A file or directory was renamed, and the provided path
|
||||
/// is the result of the rename
|
||||
RenameTo,
|
||||
|
||||
/// A file's size changed
|
||||
Size,
|
||||
|
||||
/// The ownership of a file or directory was changed
|
||||
Ownership,
|
||||
|
||||
/// The permissions of a file or directory was changed
|
||||
Permissions,
|
||||
|
||||
/// The write or modify time of a file or directory was changed
|
||||
WriteTime,
|
||||
|
||||
// Catchall in case we have no insight as to the type of change
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ChangeKind {
|
||||
/// Returns a list of all variants as str names
|
||||
pub const fn variants() -> &'static [&'static str] {
|
||||
Self::VARIANTS
|
||||
}
|
||||
|
||||
/// Returns a list of all variants as a vec
|
||||
pub fn all() -> Vec<ChangeKind> {
|
||||
ChangeKindSet::all().into_sorted_vec()
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of access
|
||||
pub fn is_access_kind(&self) -> bool {
|
||||
self.is_open_access_kind()
|
||||
|| self.is_close_access_kind()
|
||||
|| matches!(self, Self::Access | Self::AccessRead)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of open access
|
||||
pub fn is_open_access_kind(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::AccessOpenExecute | Self::AccessOpenRead | Self::AccessOpenWrite
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of close access
|
||||
pub fn is_close_access_kind(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::AccessCloseExecute | Self::AccessCloseRead | Self::AccessCloseWrite
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of creation
|
||||
pub fn is_create_kind(&self) -> bool {
|
||||
matches!(self, Self::Create)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of modification
|
||||
pub fn is_modify_kind(&self) -> bool {
|
||||
self.is_data_modify_kind() || self.is_metadata_modify_kind() || matches!(self, Self::Modify)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of data modification
|
||||
pub fn is_data_modify_kind(&self) -> bool {
|
||||
matches!(self, Self::Content | Self::Data | Self::Size)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of metadata modification
|
||||
pub fn is_metadata_modify_kind(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::AccessTime
|
||||
| Self::Metadata
|
||||
| Self::Ownership
|
||||
| Self::Permissions
|
||||
| Self::WriteTime
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of rename
|
||||
pub fn is_rename_kind(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Rename | Self::RenameBoth | Self::RenameFrom | Self::RenameTo
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if the change is a kind of removal
|
||||
pub fn is_remove_kind(&self) -> bool {
|
||||
matches!(self, Self::Remove)
|
||||
}
|
||||
|
||||
/// Returns true if the change kind is unknown
|
||||
pub fn is_unknown_kind(&self) -> bool {
|
||||
matches!(self, Self::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl ChangeKind {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(ChangeKind)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for ChangeKind {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
let mut set = ChangeKindSet::empty();
|
||||
set.insert(self);
|
||||
set.insert(rhs);
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
impl From<NotifyEventKind> for ChangeKind {
|
||||
fn from(x: NotifyEventKind) -> Self {
|
||||
use notify::event::{
|
||||
AccessKind, AccessMode, DataChange, MetadataKind, ModifyKind, RenameMode,
|
||||
};
|
||||
match x {
|
||||
// File/directory access events
|
||||
NotifyEventKind::Access(AccessKind::Read) => Self::AccessRead,
|
||||
NotifyEventKind::Access(AccessKind::Open(AccessMode::Execute)) => {
|
||||
Self::AccessOpenExecute
|
||||
}
|
||||
NotifyEventKind::Access(AccessKind::Open(AccessMode::Read)) => Self::AccessOpenRead,
|
||||
NotifyEventKind::Access(AccessKind::Open(AccessMode::Write)) => Self::AccessOpenWrite,
|
||||
NotifyEventKind::Access(AccessKind::Close(AccessMode::Execute)) => {
|
||||
Self::AccessCloseExecute
|
||||
}
|
||||
NotifyEventKind::Access(AccessKind::Close(AccessMode::Read)) => Self::AccessCloseRead,
|
||||
NotifyEventKind::Access(AccessKind::Close(AccessMode::Write)) => Self::AccessCloseWrite,
|
||||
NotifyEventKind::Access(_) => Self::Access,
|
||||
|
||||
// File/directory creation events
|
||||
NotifyEventKind::Create(_) => Self::Create,
|
||||
|
||||
// Rename-oriented events
|
||||
NotifyEventKind::Modify(ModifyKind::Name(RenameMode::Both)) => Self::RenameBoth,
|
||||
NotifyEventKind::Modify(ModifyKind::Name(RenameMode::From)) => Self::RenameFrom,
|
||||
NotifyEventKind::Modify(ModifyKind::Name(RenameMode::To)) => Self::RenameTo,
|
||||
NotifyEventKind::Modify(ModifyKind::Name(_)) => Self::Rename,
|
||||
|
||||
// Data-modification events
|
||||
NotifyEventKind::Modify(ModifyKind::Data(DataChange::Content)) => Self::Content,
|
||||
NotifyEventKind::Modify(ModifyKind::Data(DataChange::Size)) => Self::Size,
|
||||
NotifyEventKind::Modify(ModifyKind::Data(_)) => Self::Data,
|
||||
|
||||
// Metadata-modification events
|
||||
NotifyEventKind::Modify(ModifyKind::Metadata(MetadataKind::AccessTime)) => {
|
||||
Self::AccessTime
|
||||
}
|
||||
NotifyEventKind::Modify(ModifyKind::Metadata(MetadataKind::WriteTime)) => {
|
||||
Self::WriteTime
|
||||
}
|
||||
NotifyEventKind::Modify(ModifyKind::Metadata(MetadataKind::Permissions)) => {
|
||||
Self::Permissions
|
||||
}
|
||||
NotifyEventKind::Modify(ModifyKind::Metadata(MetadataKind::Ownership)) => {
|
||||
Self::Ownership
|
||||
}
|
||||
NotifyEventKind::Modify(ModifyKind::Metadata(_)) => Self::Metadata,
|
||||
|
||||
// General modification events
|
||||
NotifyEventKind::Modify(_) => Self::Modify,
|
||||
|
||||
// File/directory removal events
|
||||
NotifyEventKind::Remove(_) => Self::Remove,
|
||||
|
||||
// Catch-all for other events
|
||||
NotifyEventKind::Any | NotifyEventKind::Other => Self::Unknown,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a distinct set of different change kinds
|
||||
#[derive(Clone, Debug, Deref, DerefMut, IntoIterator, Serialize, Deserialize)]
|
||||
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
|
||||
pub struct ChangeKindSet(HashSet<ChangeKind>);
|
||||
|
||||
impl ChangeKindSet {
|
||||
/// Produces an empty set of [`ChangeKind`]
|
||||
pub fn empty() -> Self {
|
||||
Self(HashSet::new())
|
||||
}
|
||||
|
||||
/// Produces a set of all [`ChangeKind`]
|
||||
pub fn all() -> Self {
|
||||
vec![
|
||||
ChangeKind::Access,
|
||||
ChangeKind::AccessCloseExecute,
|
||||
ChangeKind::AccessCloseRead,
|
||||
ChangeKind::AccessCloseWrite,
|
||||
ChangeKind::AccessOpenExecute,
|
||||
ChangeKind::AccessOpenRead,
|
||||
ChangeKind::AccessOpenWrite,
|
||||
ChangeKind::AccessRead,
|
||||
ChangeKind::AccessTime,
|
||||
ChangeKind::Create,
|
||||
ChangeKind::Content,
|
||||
ChangeKind::Data,
|
||||
ChangeKind::Metadata,
|
||||
ChangeKind::Modify,
|
||||
ChangeKind::Remove,
|
||||
ChangeKind::Rename,
|
||||
ChangeKind::RenameBoth,
|
||||
ChangeKind::RenameFrom,
|
||||
ChangeKind::RenameTo,
|
||||
ChangeKind::Size,
|
||||
ChangeKind::Ownership,
|
||||
ChangeKind::Permissions,
|
||||
ChangeKind::WriteTime,
|
||||
ChangeKind::Unknown,
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the access kinds
|
||||
pub fn access_set() -> Self {
|
||||
Self::access_open_set()
|
||||
| Self::access_close_set()
|
||||
| ChangeKind::AccessRead
|
||||
| ChangeKind::Access
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the open access kinds
|
||||
pub fn access_open_set() -> Self {
|
||||
ChangeKind::AccessOpenExecute | ChangeKind::AccessOpenRead | ChangeKind::AccessOpenWrite
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the close access kinds
|
||||
pub fn access_close_set() -> Self {
|
||||
ChangeKind::AccessCloseExecute | ChangeKind::AccessCloseRead | ChangeKind::AccessCloseWrite
|
||||
}
|
||||
|
||||
// Produces a changeset containing all of the modification kinds
|
||||
pub fn modify_set() -> Self {
|
||||
Self::modify_data_set() | Self::modify_metadata_set() | ChangeKind::Modify
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the data modification kinds
|
||||
pub fn modify_data_set() -> Self {
|
||||
ChangeKind::Content | ChangeKind::Data | ChangeKind::Size
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the metadata modification kinds
|
||||
pub fn modify_metadata_set() -> Self {
|
||||
ChangeKind::AccessTime
|
||||
| ChangeKind::Metadata
|
||||
| ChangeKind::Ownership
|
||||
| ChangeKind::Permissions
|
||||
| ChangeKind::WriteTime
|
||||
}
|
||||
|
||||
/// Produces a changeset containing all of the rename kinds
|
||||
pub fn rename_set() -> Self {
|
||||
ChangeKind::Rename | ChangeKind::RenameBoth | ChangeKind::RenameFrom | ChangeKind::RenameTo
|
||||
}
|
||||
|
||||
/// Consumes set and returns a sorted vec of the kinds of changes
|
||||
pub fn into_sorted_vec(self) -> Vec<ChangeKind> {
|
||||
let mut v = self.0.into_iter().collect::<Vec<_>>();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "schemars")]
|
||||
impl ChangeKindSet {
|
||||
pub fn root_schema() -> schemars::schema::RootSchema {
|
||||
schemars::schema_for!(ChangeKindSet)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ChangeKindSet {
|
||||
/// Outputs a comma-separated series of [`ChangeKind`] as string that are sorted
|
||||
/// such that this will always be consistent output
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut kinds = self
|
||||
.0
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
kinds.sort_unstable();
|
||||
write!(f, "{}", kinds.join(","))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ChangeKindSet {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.to_string() == other.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ChangeKindSet {}
|
||||
|
||||
impl Hash for ChangeKindSet {
|
||||
/// Hashes based on the output of [`fmt::Display`]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.to_string().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKindSet> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: ChangeKindSet) -> Self::Output {
|
||||
self.extend(rhs.0);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKind> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: ChangeKind) -> Self::Output {
|
||||
self.0.insert(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKindSet> for ChangeKind {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn bitor(self, rhs: ChangeKindSet) -> Self::Output {
|
||||
rhs | self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<ChangeKindSet> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
ChangeKindSet(&self.0 - &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&'_ ChangeKindSet> for &ChangeKindSet {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn sub(self, other: &ChangeKindSet) -> Self::Output {
|
||||
ChangeKindSet(&self.0 - &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ChangeKindSet {
|
||||
type Err = strum::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut change_set = HashSet::new();
|
||||
|
||||
for word in s.split(',') {
|
||||
change_set.insert(ChangeKind::from_str(word.trim())?);
|
||||
}
|
||||
|
||||
Ok(ChangeKindSet(change_set))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<ChangeKind> for ChangeKindSet {
|
||||
fn from_iter<I: IntoIterator<Item = ChangeKind>>(iter: I) -> Self {
|
||||
let mut change_set = HashSet::new();
|
||||
|
||||
for i in iter {
|
||||
change_set.insert(i);
|
||||
}
|
||||
|
||||
ChangeKindSet(change_set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangeKind> for ChangeKindSet {
|
||||
fn from(change_kind: ChangeKind) -> Self {
|
||||
let mut set = Self::empty();
|
||||
set.insert(change_kind);
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChangeKind>> for ChangeKindSet {
|
||||
fn from(changes: Vec<ChangeKind>) -> Self {
|
||||
changes.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChangeKindSet {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
@ -0,0 +1,29 @@
|
||||
[package]
|
||||
name = "distant-protocol"
|
||||
description = "Protocol library for distant, providing data structures used between the client and server"
|
||||
categories = ["data-structures"]
|
||||
keywords = ["protocol"]
|
||||
version = "0.20.0-alpha.7"
|
||||
authors = ["Chip Senkbeil <chip@senkbeil.org>"]
|
||||
edition = "2021"
|
||||
homepage = "https://github.com/chipsenkbeil/distant"
|
||||
repository = "https://github.com/chipsenkbeil/distant"
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
tests = []
|
||||
|
||||
[dependencies]
|
||||
bitflags = "2.0.2"
|
||||
derive_more = { version = "0.99.17", default-features = false, features = ["deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant"] }
|
||||
regex = "1.7.3"
|
||||
serde = { version = "1.0.159", features = ["derive"] }
|
||||
serde_bytes = "0.11.9"
|
||||
strum = { version = "0.24.1", features = ["derive"] }
|
||||
|
||||
[dev-dependencies]
|
||||
rmp = "0.8.11"
|
||||
rmp-serde = "1.1.1"
|
||||
serde_json = "1.0.96"
|
@ -0,0 +1,24 @@
|
||||
mod capabilities;
|
||||
mod change;
|
||||
mod cmd;
|
||||
mod error;
|
||||
mod filesystem;
|
||||
mod metadata;
|
||||
mod permissions;
|
||||
mod pty;
|
||||
mod search;
|
||||
mod system;
|
||||
|
||||
pub use capabilities::*;
|
||||
pub use change::*;
|
||||
pub use cmd::*;
|
||||
pub use error::*;
|
||||
pub use filesystem::*;
|
||||
pub use metadata::*;
|
||||
pub use permissions::*;
|
||||
pub use pty::*;
|
||||
pub use search::*;
|
||||
pub use system::*;
|
||||
|
||||
/// Id for a remote process
|
||||
pub type ProcessId = u32;
|
@ -0,0 +1,355 @@
|
||||
use std::cmp::Ordering;
|
||||
use std::collections::HashSet;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::ops::{BitAnd, BitOr, BitXor};
|
||||
use std::str::FromStr;
|
||||
|
||||
use derive_more::{From, Into, IntoIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumMessage, IntoEnumIterator};
|
||||
|
||||
/// Represents the kinds of capabilities available.
|
||||
pub use crate::request::RequestKind as CapabilityKind;
|
||||
|
||||
/// Set of supported capabilities for a server
|
||||
#[derive(Clone, Debug, From, Into, PartialEq, Eq, IntoIterator, Serialize, Deserialize)]
|
||||
#[serde(transparent)]
|
||||
pub struct Capabilities(#[into_iterator(owned, ref)] HashSet<Capability>);
|
||||
|
||||
impl Capabilities {
|
||||
/// Return set of capabilities encompassing all possible capabilities
|
||||
pub fn all() -> Self {
|
||||
Self(CapabilityKind::iter().map(Capability::from).collect())
|
||||
}
|
||||
|
||||
/// Return empty set of capabilities
|
||||
pub fn none() -> Self {
|
||||
Self(HashSet::new())
|
||||
}
|
||||
|
||||
/// Returns true if the capability with described kind is included
|
||||
pub fn contains(&self, kind: impl AsRef<str>) -> bool {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.contains(&cap)
|
||||
}
|
||||
|
||||
/// Adds the specified capability to the set of capabilities
|
||||
///
|
||||
/// * If the set did not have this capability, returns `true`
|
||||
/// * If the set did have this capability, returns `false`
|
||||
pub fn insert(&mut self, cap: impl Into<Capability>) -> bool {
|
||||
self.0.insert(cap.into())
|
||||
}
|
||||
|
||||
/// Removes the capability with the described kind, returning the capability
|
||||
pub fn take(&mut self, kind: impl AsRef<str>) -> Option<Capability> {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.take(&cap)
|
||||
}
|
||||
|
||||
/// Removes the capability with the described kind, returning true if it existed
|
||||
pub fn remove(&mut self, kind: impl AsRef<str>) -> bool {
|
||||
let cap = Capability {
|
||||
kind: kind.as_ref().to_string(),
|
||||
description: String::new(),
|
||||
};
|
||||
self.0.remove(&cap)
|
||||
}
|
||||
|
||||
/// Converts into vec of capabilities sorted by kind
|
||||
pub fn into_sorted_vec(self) -> Vec<Capability> {
|
||||
let mut this = self.0.into_iter().collect::<Vec<_>>();
|
||||
|
||||
this.sort_unstable();
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
impl BitAnd for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitand(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitand(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitor(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<Capability> for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitor(self, rhs: Capability) -> Self::Output {
|
||||
let mut other = Capabilities::none();
|
||||
other.0.insert(rhs);
|
||||
|
||||
self.bitor(&other)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitXor for &Capabilities {
|
||||
type Output = Capabilities;
|
||||
|
||||
fn bitxor(self, rhs: Self) -> Self::Output {
|
||||
Capabilities(self.0.bitxor(&rhs.0))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<Capability> for Capabilities {
|
||||
fn from_iter<I: IntoIterator<Item = Capability>>(iter: I) -> Self {
|
||||
let mut this = Capabilities::none();
|
||||
|
||||
for capability in iter {
|
||||
this.0.insert(capability);
|
||||
}
|
||||
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/// Capability tied to a server. A capability is equivalent based on its kind and not description.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub struct Capability {
|
||||
/// Label describing the kind of capability
|
||||
pub kind: String,
|
||||
|
||||
/// Information about the capability
|
||||
pub description: String,
|
||||
}
|
||||
|
||||
impl Capability {
|
||||
/// Will convert the [`Capability`]'s `kind` into a known [`CapabilityKind`] if possible,
|
||||
/// returning None if the capability is unknown
|
||||
pub fn to_capability_kind(&self) -> Option<CapabilityKind> {
|
||||
CapabilityKind::from_str(&self.kind).ok()
|
||||
}
|
||||
|
||||
/// Returns true if the described capability is unknown
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
self.to_capability_kind().is_none()
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Capability {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.kind.eq_ignore_ascii_case(&other.kind)
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Capability {}
|
||||
|
||||
impl PartialOrd for Capability {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl Ord for Capability {
|
||||
fn cmp(&self, other: &Self) -> Ordering {
|
||||
self.kind
|
||||
.to_ascii_lowercase()
|
||||
.cmp(&other.kind.to_ascii_lowercase())
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for Capability {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.kind.to_ascii_lowercase().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl From<CapabilityKind> for Capability {
|
||||
/// Creates a new capability using the kind's default message
|
||||
fn from(kind: CapabilityKind) -> Self {
|
||||
Self {
|
||||
kind: kind.to_string(),
|
||||
description: kind
|
||||
.get_message()
|
||||
.map(ToString::to_string)
|
||||
.unwrap_or_default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
mod capabilities {
|
||||
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_serialize_to_json() {
|
||||
let capabilities: Capabilities = [Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
let value = serde_json::to_value(capabilities).unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
serde_json::json!([
|
||||
{
|
||||
"kind": "some kind",
|
||||
"description": "some description",
|
||||
}
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_deserialize_from_json() {
|
||||
let value = serde_json::json!([
|
||||
{
|
||||
"kind": "some kind",
|
||||
"description": "some description",
|
||||
}
|
||||
]);
|
||||
|
||||
let capabilities: Capabilities = serde_json::from_value(value).unwrap();
|
||||
assert_eq!(
|
||||
capabilities,
|
||||
[Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_serialize_to_msgpack() {
|
||||
let capabilities: Capabilities = [Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect();
|
||||
|
||||
// NOTE: We don't actually check the output here because it's an implementation detail
|
||||
// and could change as we change how serialization is done. This is merely to verify
|
||||
// that we can serialize since there are times when serde fails to serialize at
|
||||
// runtime.
|
||||
let _ = rmp_serde::to_vec(&capabilities).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_deserialize_from_msgpack() {
|
||||
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
|
||||
// verify that we are not corrupting or preventing issues when serializing on a
|
||||
// client/server and then trying to deserialize on the other side. This has happened
|
||||
// enough times with minor changes that we need tests to verify.
|
||||
let buf = rmp_serde::to_vec(
|
||||
&[Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect::<Capabilities>(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let capabilities: Capabilities = rmp_serde::from_slice(&buf).unwrap();
|
||||
assert_eq!(
|
||||
capabilities,
|
||||
[Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}]
|
||||
.into_iter()
|
||||
.collect()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod capability {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_serialize_to_json() {
|
||||
let capability = Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
};
|
||||
|
||||
let value = serde_json::to_value(capability).unwrap();
|
||||
assert_eq!(
|
||||
value,
|
||||
serde_json::json!({
|
||||
"kind": "some kind",
|
||||
"description": "some description",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_deserialize_from_json() {
|
||||
let value = serde_json::json!({
|
||||
"kind": "some kind",
|
||||
"description": "some description",
|
||||
});
|
||||
|
||||
let capability: Capability = serde_json::from_value(value).unwrap();
|
||||
assert_eq!(
|
||||
capability,
|
||||
Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_serialize_to_msgpack() {
|
||||
let capability = Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
};
|
||||
|
||||
// NOTE: We don't actually check the output here because it's an implementation detail
|
||||
// and could change as we change how serialization is done. This is merely to verify
|
||||
// that we can serialize since there are times when serde fails to serialize at
|
||||
// runtime.
|
||||
let _ = rmp_serde::to_vec(&capability).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn should_be_able_to_deserialize_from_msgpack() {
|
||||
// NOTE: It may seem odd that we are serializing just to deserialize, but this is to
|
||||
// verify that we are not corrupting or preventing issues when serializing on a
|
||||
// client/server and then trying to deserialize on the other side. This has happened
|
||||
// enough times with minor changes that we need tests to verify.
|
||||
let buf = rmp_serde::to_vec(&Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let capability: Capability = rmp_serde::from_slice(&buf).unwrap();
|
||||
assert_eq!(
|
||||
capability,
|
||||
Capability {
|
||||
kind: "some kind".to_string(),
|
||||
description: "some description".to_string(),
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,280 @@
|
||||
use std::collections::HashSet;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::FromIterator;
|
||||
use std::ops::{BitOr, Sub};
|
||||
use std::path::PathBuf;
|
||||
use std::str::FromStr;
|
||||
|
||||
use derive_more::{Deref, DerefMut, IntoIterator};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{EnumString, EnumVariantNames, VariantNames};
|
||||
|
||||
/// Change to one or more paths on the filesystem.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
pub struct Change {
|
||||
/// Label describing the kind of change
|
||||
pub kind: ChangeKind,
|
||||
|
||||
/// Paths that were changed
|
||||
pub paths: Vec<PathBuf>,
|
||||
}
|
||||
|
||||
/// Represents a label attached to a [`Change`] that describes the kind of change.
|
||||
///
|
||||
/// This mirrors events seen from `incron`.
|
||||
#[derive(
|
||||
Copy,
|
||||
Clone,
|
||||
Debug,
|
||||
strum::Display,
|
||||
EnumString,
|
||||
EnumVariantNames,
|
||||
Hash,
|
||||
PartialEq,
|
||||
Eq,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
)]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields)]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum ChangeKind {
|
||||
/// A file was read
|
||||
Access,
|
||||
|
||||
/// A file's or directory's attributes were changed
|
||||
Attribute,
|
||||
|
||||
/// A file open for writing was closed
|
||||
CloseWrite,
|
||||
|
||||
/// A file not open for writing was closed
|
||||
CloseNoWrite,
|
||||
|
||||
/// A file, directory, or something else was created within a watched directory
|
||||
Create,
|
||||
|
||||
/// A file, directory, or something else was deleted within a watched directory
|
||||
Delete,
|
||||
|
||||
/// A watched file or directory was deleted
|
||||
DeleteSelf,
|
||||
|
||||
/// A file's content was modified
|
||||
Modify,
|
||||
|
||||
/// A file, directory, or something else was moved out of a watched directory
|
||||
MoveFrom,
|
||||
|
||||
/// A watched file or directory was moved
|
||||
MoveSelf,
|
||||
|
||||
/// A file, directory, or something else was moved into a watched directory
|
||||
MoveTo,
|
||||
|
||||
/// A file was opened
|
||||
Open,
|
||||
|
||||
/// Catch-all for any other change
|
||||
Unknown,
|
||||
}
|
||||
|
||||
impl ChangeKind {
|
||||
/// Returns a list of all variants as str names
|
||||
pub const fn variants() -> &'static [&'static str] {
|
||||
Self::VARIANTS
|
||||
}
|
||||
|
||||
/// Returns a list of all variants as a vec
|
||||
pub fn all() -> Vec<ChangeKind> {
|
||||
ChangeKindSet::all().into_sorted_vec()
|
||||
}
|
||||
|
||||
/// Returns true if kind is part of the access family.
|
||||
pub fn is_access(&self) -> bool {
|
||||
matches!(
|
||||
self,
|
||||
Self::Access | Self::CloseWrite | Self::CloseNoWrite | Self::Open
|
||||
)
|
||||
}
|
||||
|
||||
/// Returns true if kind is part of the modify family.
|
||||
pub fn is_modify(&self) -> bool {
|
||||
matches!(self, Self::Attribute | Self::Modify)
|
||||
}
|
||||
|
||||
/// Returns true if kind is unknown.
|
||||
pub fn is_unknown(&self) -> bool {
|
||||
matches!(self, Self::Unknown)
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr for ChangeKind {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn bitor(self, rhs: Self) -> Self::Output {
|
||||
let mut set = ChangeKindSet::empty();
|
||||
set.insert(self);
|
||||
set.insert(rhs);
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
/// Represents a distinct set of different change kinds
|
||||
#[derive(Clone, Debug, Deref, DerefMut, IntoIterator, Serialize, Deserialize)]
|
||||
pub struct ChangeKindSet(HashSet<ChangeKind>);
|
||||
|
||||
impl ChangeKindSet {
|
||||
/// Produces an empty set of [`ChangeKind`]
|
||||
pub fn empty() -> Self {
|
||||
Self(HashSet::new())
|
||||
}
|
||||
|
||||
/// Produces a set of all [`ChangeKind`]
|
||||
pub fn all() -> Self {
|
||||
vec![
|
||||
ChangeKind::Access,
|
||||
ChangeKind::Attribute,
|
||||
ChangeKind::CloseWrite,
|
||||
ChangeKind::CloseNoWrite,
|
||||
ChangeKind::Create,
|
||||
ChangeKind::Delete,
|
||||
ChangeKind::DeleteSelf,
|
||||
ChangeKind::Modify,
|
||||
ChangeKind::MoveFrom,
|
||||
ChangeKind::MoveSelf,
|
||||
ChangeKind::MoveTo,
|
||||
ChangeKind::Open,
|
||||
ChangeKind::Unknown,
|
||||
]
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Consumes set and returns a sorted vec of the kinds of changes
|
||||
pub fn into_sorted_vec(self) -> Vec<ChangeKind> {
|
||||
let mut v = self.0.into_iter().collect::<Vec<_>>();
|
||||
v.sort();
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for ChangeKindSet {
|
||||
/// Outputs a comma-separated series of [`ChangeKind`] as string that are sorted
|
||||
/// such that this will always be consistent output
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let mut kinds = self
|
||||
.0
|
||||
.iter()
|
||||
.map(ToString::to_string)
|
||||
.collect::<Vec<String>>();
|
||||
kinds.sort_unstable();
|
||||
write!(f, "{}", kinds.join(","))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for ChangeKindSet {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.to_string() == other.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for ChangeKindSet {}
|
||||
|
||||
impl Hash for ChangeKindSet {
|
||||
/// Hashes based on the output of [`fmt::Display`]
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.to_string().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKindSet> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: ChangeKindSet) -> Self::Output {
|
||||
self.extend(rhs.0);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKind> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn bitor(mut self, rhs: ChangeKind) -> Self::Output {
|
||||
self.0.insert(rhs);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl BitOr<ChangeKindSet> for ChangeKind {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn bitor(self, rhs: ChangeKindSet) -> Self::Output {
|
||||
rhs | self
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<ChangeKindSet> for ChangeKindSet {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, other: Self) -> Self::Output {
|
||||
ChangeKindSet(&self.0 - &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<&'_ ChangeKindSet> for &ChangeKindSet {
|
||||
type Output = ChangeKindSet;
|
||||
|
||||
fn sub(self, other: &ChangeKindSet) -> Self::Output {
|
||||
ChangeKindSet(&self.0 - &other.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for ChangeKindSet {
|
||||
type Err = strum::ParseError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut change_set = HashSet::new();
|
||||
|
||||
for word in s.split(',') {
|
||||
change_set.insert(ChangeKind::from_str(word.trim())?);
|
||||
}
|
||||
|
||||
Ok(ChangeKindSet(change_set))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromIterator<ChangeKind> for ChangeKindSet {
|
||||
fn from_iter<I: IntoIterator<Item = ChangeKind>>(iter: I) -> Self {
|
||||
let mut change_set = HashSet::new();
|
||||
|
||||
for i in iter {
|
||||
change_set.insert(i);
|
||||
}
|
||||
|
||||
ChangeKindSet(change_set)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ChangeKind> for ChangeKindSet {
|
||||
fn from(change_kind: ChangeKind) -> Self {
|
||||
let mut set = Self::empty();
|
||||
set.insert(change_kind);
|
||||
set
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Vec<ChangeKind>> for ChangeKindSet {
|
||||
fn from(changes: Vec<ChangeKind>) -> Self {
|
||||
changes.into_iter().collect()
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for ChangeKindSet {
|
||||
fn default() -> Self {
|
||||
Self::empty()
|
||||
}
|
||||
}
|
@ -0,0 +1,10 @@
|
||||
mod common;
|
||||
mod msg;
|
||||
mod request;
|
||||
mod response;
|
||||
mod utils;
|
||||
|
||||
pub use common::*;
|
||||
pub use msg::*;
|
||||
pub use request::*;
|
||||
pub use response::*;
|
@ -0,0 +1,78 @@
|
||||
use derive_more::From;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
/// Represents a wrapper around a distant message, supporting single and batch requests
|
||||
#[derive(Clone, Debug, From, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum Msg<T> {
|
||||
Single(T),
|
||||
Batch(Vec<T>),
|
||||
}
|
||||
|
||||
impl<T> Msg<T> {
|
||||
/// Returns true if msg has a single payload
|
||||
pub fn is_single(&self) -> bool {
|
||||
matches!(self, Self::Single(_))
|
||||
}
|
||||
|
||||
/// Returns reference to single value if msg is single variant
|
||||
pub fn as_single(&self) -> Option<&T> {
|
||||
match self {
|
||||
Self::Single(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns mutable reference to single value if msg is single variant
|
||||
pub fn as_mut_single(&mut self) -> Option<&T> {
|
||||
match self {
|
||||
Self::Single(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the single value if msg is single variant
|
||||
pub fn into_single(self) -> Option<T> {
|
||||
match self {
|
||||
Self::Single(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns true if msg has a batch of payloads
|
||||
pub fn is_batch(&self) -> bool {
|
||||
matches!(self, Self::Batch(_))
|
||||
}
|
||||
|
||||
/// Returns reference to batch value if msg is batch variant
|
||||
pub fn as_batch(&self) -> Option<&[T]> {
|
||||
match self {
|
||||
Self::Batch(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns mutable reference to batch value if msg is batch variant
|
||||
pub fn as_mut_batch(&mut self) -> Option<&mut [T]> {
|
||||
match self {
|
||||
Self::Batch(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the batch value if msg is batch variant
|
||||
pub fn into_batch(self) -> Option<Vec<T>> {
|
||||
match self {
|
||||
Self::Batch(x) => Some(x),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert into a collection of payload data
|
||||
pub fn into_vec(self) -> Vec<T> {
|
||||
match self {
|
||||
Self::Single(x) => vec![x],
|
||||
Self::Batch(x) => x,
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
use std::io;
|
||||
|
||||
use derive_more::IsVariant;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString};
|
||||
|
||||
use crate::common::{
|
||||
Capabilities, Change, DirEntry, Error, Metadata, ProcessId, SearchId, SearchQueryMatch,
|
||||
SystemInfo,
|
||||
};
|
||||
|
||||
/// Represents the payload of a successful response
|
||||
#[derive(
|
||||
Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, EnumDiscriminants, Serialize, Deserialize,
|
||||
)]
|
||||
#[strum_discriminants(derive(
|
||||
AsRefStr,
|
||||
strum::Display,
|
||||
EnumIter,
|
||||
EnumMessage,
|
||||
EnumString,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
IsVariant,
|
||||
Serialize,
|
||||
Deserialize
|
||||
))]
|
||||
#[strum_discriminants(name(ResponseKind))]
|
||||
#[strum_discriminants(strum(serialize_all = "snake_case"))]
|
||||
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")]
|
||||
#[strum(serialize_all = "snake_case")]
|
||||
pub enum Response {
|
||||
/// General okay with no extra data, returned in cases like
|
||||
/// creating or removing a directory, copying a file, or renaming
|
||||
/// a file
|
||||
Ok,
|
||||
|
||||
/// General-purpose failure that occurred from some request
|
||||
Error(Error),
|
||||
|
||||
/// Response containing some arbitrary, binary data
|
||||
Blob {
|
||||
/// Binary data associated with the response
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Response containing some arbitrary, text data
|
||||
Text {
|
||||
/// Text data associated with the response
|
||||
data: String,
|
||||
},
|
||||
|
||||
/// Response to reading a directory
|
||||
DirEntries {
|
||||
/// Entries contained within the requested directory
|
||||
entries: Vec<DirEntry>,
|
||||
|
||||
/// Errors encountered while scanning for entries
|
||||
errors: Vec<Error>,
|
||||
},
|
||||
|
||||
/// Response to a filesystem change for some watched file, directory, or symlink
|
||||
Changed(Change),
|
||||
|
||||
/// Response to checking if a path exists
|
||||
Exists { value: bool },
|
||||
|
||||
/// Represents metadata about some filesystem object (file, directory, symlink) on remote machine
|
||||
Metadata(Metadata),
|
||||
|
||||
/// Represents a search being started
|
||||
SearchStarted {
|
||||
/// Arbitrary id associated with search
|
||||
id: SearchId,
|
||||
},
|
||||
|
||||
/// Represents some subset of results for a search query (may not be all of them)
|
||||
SearchResults {
|
||||
/// Arbitrary id associated with search
|
||||
id: SearchId,
|
||||
|
||||
/// Collection of matches from performing a query
|
||||
matches: Vec<SearchQueryMatch>,
|
||||
},
|
||||
|
||||
/// Represents a search being completed
|
||||
SearchDone {
|
||||
/// Arbitrary id associated with search
|
||||
id: SearchId,
|
||||
},
|
||||
|
||||
/// Response to starting a new process
|
||||
ProcSpawned {
|
||||
/// Arbitrary id associated with running process
|
||||
id: ProcessId,
|
||||
},
|
||||
|
||||
/// Actively-transmitted stdout as part of running process
|
||||
ProcStdout {
|
||||
/// Arbitrary id associated with running process
|
||||
id: ProcessId,
|
||||
|
||||
/// Data read from a process' stdout pipe
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Actively-transmitted stderr as part of running process
|
||||
ProcStderr {
|
||||
/// Arbitrary id associated with running process
|
||||
id: ProcessId,
|
||||
|
||||
/// Data read from a process' stderr pipe
|
||||
#[serde(with = "serde_bytes")]
|
||||
data: Vec<u8>,
|
||||
},
|
||||
|
||||
/// Response to a process finishing
|
||||
ProcDone {
|
||||
/// Arbitrary id associated with running process
|
||||
id: ProcessId,
|
||||
|
||||
/// Whether or not termination was successful
|
||||
success: bool,
|
||||
|
||||
/// Exit code associated with termination, will be missing if terminated by signal
|
||||
code: Option<i32>,
|
||||
},
|
||||
|
||||
/// Response to retrieving information about the server and the system it is on
|
||||
SystemInfo(SystemInfo),
|
||||
|
||||
/// Response to retrieving information about the server's capabilities
|
||||
Capabilities { supported: Capabilities },
|
||||
}
|
||||
|
||||
impl From<io::Error> for Response {
|
||||
fn from(x: io::Error) -> Self {
|
||||
Self::Error(Error::from(x))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue