Initial commit

pull/189/head
Chip Senkbeil 1 year ago
parent 95c0d0c0d1
commit 793844ccbe
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

20
Cargo.lock generated

@ -872,6 +872,7 @@ dependencies = [
"bytes",
"derive_more",
"distant-net",
"distant-protocol",
"env_logger",
"futures",
"grep",
@ -931,6 +932,21 @@ dependencies = [
"tokio",
]
[[package]]
name = "distant-protocol"
version = "0.20.0-alpha.7"
dependencies = [
"bitflags 2.3.1",
"derive_more",
"regex",
"rmp",
"rmp-serde",
"serde",
"serde_bytes",
"serde_json",
"strum",
]
[[package]]
name = "distant-ssh2"
version = "0.20.0-alpha.7"
@ -1934,9 +1950,9 @@ checksum = "61807f77802ff30975e01f4f071c8ba10c022052f98b3294119f3e615d13e5be"
[[package]]
name = "notify"
version = "5.2.0"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "729f63e1ca555a43fe3efa4f3efdf4801c479da85b432242a7b726f353c88486"
checksum = "4d9ba6c734de18ca27c8cef5cd7058aa4ac9f63596131e4c7e41e579319032a2"
dependencies = [
"bitflags 1.3.2",
"crossbeam-channel",

@ -12,7 +12,7 @@ readme = "README.md"
license = "MIT OR Apache-2.0"
[workspace]
members = ["distant-auth", "distant-core", "distant-net", "distant-ssh2"]
members = ["distant-auth", "distant-core", "distant-net", "distant-protocol", "distant-ssh2"]
[profile.release]
opt-level = 'z'

@ -20,12 +20,13 @@ bitflags = "2.0.2"
bytes = "1.4.0"
derive_more = { version = "0.99.17", default-features = false, features = ["as_mut", "as_ref", "deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant", "try_into"] }
distant-net = { version = "=0.20.0-alpha.7", path = "../distant-net" }
distant-protocol = { version = "=0.20.0-alpha.7", path = "../distant-protocol" }
futures = "0.3.28"
grep = "0.2.11"
hex = "0.4.3"
ignore = "0.4.20"
log = "0.4.17"
notify = { version = "5.1.0", features = ["serde"] }
notify = { version = "6.0.0", features = ["serde"] }
num_cpus = "1.15.0"
once_cell = "1.17.1"
portable-pty = "0.8.1"

@ -1,5 +1,6 @@
use std::io;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::{env, io};
use async_trait::async_trait;
use ignore::{DirEntry as WalkDirEntry, WalkBuilder};
@ -409,7 +410,66 @@ impl DistantApi for LocalDistantApi {
"[Conn {}] Reading metadata for {:?} {{canonicalize: {}, resolve_file_type: {}}}",
ctx.connection_id, path, canonicalize, resolve_file_type
);
Metadata::read(path, canonicalize, resolve_file_type).await
let metadata = tokio::fs::symlink_metadata(path.as_path()).await?;
let canonicalized_path = if canonicalize {
Some(tokio::fs::canonicalize(path.as_path()).await?)
} else {
None
};
// If asking for resolved file type and current type is symlink, then we want to refresh
// our metadata to get the filetype for the resolved link
let file_type = if resolve_file_type && metadata.file_type().is_symlink() {
tokio::fs::metadata(path).await?.file_type()
} else {
metadata.file_type()
};
Ok(Metadata {
canonicalized_path,
accessed: metadata
.accessed()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
created: metadata
.created()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
modified: metadata
.modified()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
len: metadata.len(),
readonly: metadata.permissions().readonly(),
file_type: if file_type.is_dir() {
FileType::Dir
} else if file_type.is_file() {
FileType::File
} else {
FileType::Symlink
},
#[cfg(unix)]
unix: Some({
use std::os::unix::prelude::*;
let mode = metadata.mode();
crate::protocol::UnixMetadata::from(mode)
}),
#[cfg(not(unix))]
unix: None,
#[cfg(windows)]
windows: Some({
use std::os::windows::prelude::*;
let attributes = metadata.file_attributes();
crate::protocol::WindowsMetadata::from(attributes)
}),
#[cfg(not(windows))]
windows: None,
})
}
async fn set_permissions(
@ -615,7 +675,19 @@ impl DistantApi for LocalDistantApi {
async fn system_info(&self, ctx: DistantCtx<Self::LocalData>) -> io::Result<SystemInfo> {
debug!("[Conn {}] Reading system information", ctx.connection_id);
Ok(SystemInfo::default())
Ok(SystemInfo {
family: env::consts::FAMILY.to_string(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
current_dir: env::current_dir().unwrap_or_default(),
main_separator: std::path::MAIN_SEPARATOR,
username: whoami::username(),
shell: if cfg!(windows) {
env::var("ComSpec").unwrap_or_else(|_| String::from("cmd.exe"))
} else {
env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
},
})
}
}

@ -7,10 +7,11 @@ pub use client::*;
mod credentials;
pub use credentials::*;
pub mod protocol;
mod constants;
mod serde_str;
/// Re-export of `distant-net` as `net`
/// Network functionality.
pub use distant_net as net;
/// Protocol structures.
pub use distant_protocol as protocol;

@ -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()
}
}

@ -5,7 +5,6 @@ use serde::{Deserialize, Serialize};
/// Represents some command with arguments to execute
#[derive(Clone, Debug, Display, From, Into, Hash, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Cmd(String);
impl Cmd {
@ -31,13 +30,6 @@ impl Cmd {
}
}
#[cfg(feature = "schemars")]
impl Cmd {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Cmd)
}
}
impl Deref for Cmd {
type Target = String;

@ -1,12 +1,10 @@
use std::io;
use derive_more::Display;
use notify::ErrorKind as NotifyErrorKind;
use serde::{Deserialize, Serialize};
/// General purpose error type that can be sent across the wire
#[derive(Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[display(fmt = "{kind}: {description}")]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct Error {
@ -26,13 +24,6 @@ impl Error {
}
}
#[cfg(feature = "schemars")]
impl Error {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Error)
}
}
impl<'a> From<&'a str> for Error {
fn from(x: &'a str) -> Self {
Self::from(x.to_string())
@ -63,76 +54,8 @@ impl From<Error> for io::Error {
}
}
impl From<notify::Error> for Error {
fn from(x: notify::Error) -> Self {
let err = match x.kind {
NotifyErrorKind::Generic(x) => Self {
kind: ErrorKind::Other,
description: x,
},
NotifyErrorKind::Io(x) => Self::from(x),
NotifyErrorKind::PathNotFound => Self {
kind: ErrorKind::Other,
description: String::from("Path not found"),
},
NotifyErrorKind::WatchNotFound => Self {
kind: ErrorKind::Other,
description: String::from("Watch not found"),
},
NotifyErrorKind::InvalidConfig(_) => Self {
kind: ErrorKind::Other,
description: String::from("Invalid config"),
},
NotifyErrorKind::MaxFilesWatch => Self {
kind: ErrorKind::Other,
description: String::from("Max files watched"),
},
};
Self {
kind: err.kind,
description: format!(
"{}\n\nPaths: {}",
err.description,
x.paths
.into_iter()
.map(|p| p.to_string_lossy().to_string())
.collect::<Vec<String>>()
.join(", ")
),
}
}
}
impl From<walkdir::Error> for Error {
fn from(x: walkdir::Error) -> Self {
if x.io_error().is_some() {
x.into_io_error().map(Self::from).unwrap()
} else {
Self {
kind: ErrorKind::Loop,
description: format!("{x}"),
}
}
}
}
impl From<tokio::task::JoinError> for Error {
fn from(x: tokio::task::JoinError) -> Self {
Self {
kind: if x.is_cancelled() {
ErrorKind::TaskCancelled
} else {
ErrorKind::TaskPanicked
},
description: format!("{x}"),
}
}
}
/// All possible kinds of errors that can be returned
#[derive(Copy, Clone, Debug, Display, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub enum ErrorKind {
/// An entity was not found, often a file
@ -211,13 +134,6 @@ pub enum ErrorKind {
Unknown,
}
#[cfg(feature = "schemars")]
impl ErrorKind {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(ErrorKind)
}
}
impl From<io::ErrorKind> for ErrorKind {
fn from(kind: io::ErrorKind) -> Self {
match kind {

@ -7,7 +7,6 @@ use strum::AsRefStr;
/// Represents information about a single entry within a directory
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
pub struct DirEntry {
/// Represents the full path to the entry
@ -21,16 +20,8 @@ pub struct DirEntry {
pub depth: usize,
}
#[cfg(feature = "schemars")]
impl DirEntry {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(DirEntry)
}
}
/// Represents the type associated with a dir entry
#[derive(Copy, Clone, Debug, Hash, PartialEq, Eq, AsRefStr, IsVariant, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields)]
#[strum(serialize_all = "snake_case")]
pub enum FileType {
@ -50,10 +41,3 @@ impl From<StdFileType> for FileType {
}
}
}
#[cfg(feature = "schemars")]
impl FileType {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(FileType)
}
}

@ -1,15 +1,13 @@
use std::io;
use std::path::{Path, PathBuf};
use std::time::SystemTime;
use std::path::PathBuf;
use bitflags::bitflags;
use serde::{Deserialize, Serialize};
use super::{deserialize_u128_option, serialize_u128_option, FileType};
use crate::common::FileType;
use crate::utils::{deserialize_u128_option, serialize_u128_option};
/// Represents metadata about some path on a remote machine
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Metadata {
/// Canonicalized path to the file or directory, resolving symlinks, only included
/// if flagged during the request
@ -49,85 +47,8 @@ pub struct Metadata {
pub windows: Option<WindowsMetadata>,
}
impl Metadata {
pub async fn read(
path: impl AsRef<Path>,
canonicalize: bool,
resolve_file_type: bool,
) -> io::Result<Self> {
let metadata = tokio::fs::symlink_metadata(path.as_ref()).await?;
let canonicalized_path = if canonicalize {
Some(tokio::fs::canonicalize(path.as_ref()).await?)
} else {
None
};
// If asking for resolved file type and current type is symlink, then we want to refresh
// our metadata to get the filetype for the resolved link
let file_type = if resolve_file_type && metadata.file_type().is_symlink() {
tokio::fs::metadata(path).await?.file_type()
} else {
metadata.file_type()
};
Ok(Self {
canonicalized_path,
accessed: metadata
.accessed()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
created: metadata
.created()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
modified: metadata
.modified()
.ok()
.and_then(|t| t.duration_since(SystemTime::UNIX_EPOCH).ok())
.map(|d| d.as_millis()),
len: metadata.len(),
readonly: metadata.permissions().readonly(),
file_type: if file_type.is_dir() {
FileType::Dir
} else if file_type.is_file() {
FileType::File
} else {
FileType::Symlink
},
#[cfg(unix)]
unix: Some({
use std::os::unix::prelude::*;
let mode = metadata.mode();
crate::protocol::UnixMetadata::from(mode)
}),
#[cfg(not(unix))]
unix: None,
#[cfg(windows)]
windows: Some({
use std::os::windows::prelude::*;
let attributes = metadata.file_attributes();
crate::protocol::WindowsMetadata::from(attributes)
}),
#[cfg(not(windows))]
windows: None,
})
}
}
#[cfg(feature = "schemars")]
impl Metadata {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Metadata)
}
}
/// Represents unix-specific metadata about some path on a remote machine
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct UnixMetadata {
/// Represents whether or not owner can read from the file
pub owner_read: bool,
@ -157,13 +78,6 @@ pub struct UnixMetadata {
pub other_exec: bool,
}
#[cfg(feature = "schemars")]
impl UnixMetadata {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(UnixMetadata)
}
}
impl From<u32> for UnixMetadata {
/// Create from a unix mode bitset
fn from(mode: u32) -> Self {
@ -243,7 +157,6 @@ bitflags! {
/// Represents windows-specific metadata about some path on a remote machine
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct WindowsMetadata {
/// Represents whether or not a file or directory is an archive
pub archive: bool,
@ -296,13 +209,6 @@ pub struct WindowsMetadata {
pub temporary: bool,
}
#[cfg(feature = "schemars")]
impl WindowsMetadata {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(WindowsMetadata)
}
}
impl From<u32> for WindowsMetadata {
/// Create from a windows file attribute bitset
fn from(file_attributes: u32) -> Self {

@ -2,7 +2,6 @@ use bitflags::bitflags;
use serde::{Deserialize, Serialize};
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(default, deny_unknown_fields, rename_all = "snake_case")]
pub struct SetPermissionsOptions {
/// Whether or not to exclude symlinks from traversal entirely, meaning that permissions will
@ -20,13 +19,6 @@ pub struct SetPermissionsOptions {
pub recursive: bool,
}
#[cfg(feature = "schemars")]
impl SetPermissionsOptions {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SetPermissionsOptions)
}
}
/// Represents permissions to apply to some path on a remote machine
///
/// When used to set permissions on a file, directory, or symlink,
@ -36,7 +28,6 @@ impl SetPermissionsOptions {
/// you would find with `chmod`. On all other platforms, this uses the
/// write flags to determine whether or not to set the readonly status.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct Permissions {
/// Represents whether or not owner can read from the file
pub owner_read: Option<bool>,
@ -70,7 +61,7 @@ impl Permissions {
/// Creates a set of [`Permissions`] that indicate readonly status.
///
/// ```
/// use distant_core::protocol::Permissions;
/// use distant_protocol::Permissions;
///
/// let permissions = Permissions::readonly();
/// assert_eq!(permissions.is_readonly(), Some(true));
@ -94,7 +85,7 @@ impl Permissions {
/// Creates a set of [`Permissions`] that indicate globally writable status.
///
/// ```
/// use distant_core::protocol::Permissions;
/// use distant_protocol::Permissions;
///
/// let permissions = Permissions::writable();
/// assert_eq!(permissions.is_readonly(), Some(false));
@ -237,13 +228,6 @@ impl Permissions {
}
}
#[cfg(feature = "schemars")]
impl Permissions {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Permissions)
}
}
#[cfg(unix)]
impl From<std::fs::Permissions> for Permissions {
/// Converts [`std::fs::Permissions`] into [`Permissions`] using

@ -3,12 +3,10 @@ use std::num::ParseIntError;
use std::str::FromStr;
use derive_more::{Display, Error};
use portable_pty::PtySize as PortablePtySize;
use serde::{Deserialize, Serialize};
/// Represents the size associated with a remote PTY
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct PtySize {
/// Number of lines of text
pub rows: u16,
@ -36,35 +34,6 @@ impl PtySize {
}
}
#[cfg(feature = "schemars")]
impl PtySize {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(PtySize)
}
}
impl From<PortablePtySize> for PtySize {
fn from(size: PortablePtySize) -> Self {
Self {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
}
}
}
impl From<PtySize> for PortablePtySize {
fn from(size: PtySize) -> Self {
Self {
rows: size.rows,
cols: size.cols,
pixel_width: size.pixel_width,
pixel_height: size.pixel_height,
}
}
}
impl fmt::Display for PtySize {
/// Prints out `rows,cols[,pixel_width,pixel_height]` where the
/// pixel width and pixel height are only included if either

@ -5,14 +5,13 @@ use std::str::FromStr;
use serde::{Deserialize, Serialize};
use super::FileType;
use crate::common::FileType;
/// Id associated with a search
pub type SearchId = u32;
/// Represents a query to perform against the filesystem
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SearchQuery {
/// Kind of data to examine using condition
pub target: SearchQueryTarget,
@ -28,25 +27,8 @@ pub struct SearchQuery {
pub options: SearchQueryOptions,
}
#[cfg(feature = "schemars")]
impl SearchQuery {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQuery)
}
}
impl FromStr for SearchQuery {
type Err = serde_json::error::Error;
/// Parses search query from a JSON string
fn from_str(s: &str) -> Result<Self, Self::Err> {
serde_json::from_str(s)
}
}
/// Kind of data to examine using conditions
#[derive(Copy, Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SearchQueryTarget {
/// Checks path of file, directory, or symlink
@ -56,16 +38,8 @@ pub enum SearchQueryTarget {
Contents,
}
#[cfg(feature = "schemars")]
impl SearchQueryTarget {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryTarget)
}
}
/// Condition used to find a match in a search query
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")]
pub enum SearchQueryCondition {
/// Text is found anywhere (all regex patterns are escaped)
@ -156,13 +130,6 @@ impl SearchQueryCondition {
}
}
#[cfg(feature = "schemars")]
impl SearchQueryCondition {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryCondition)
}
}
impl FromStr for SearchQueryCondition {
type Err = std::convert::Infallible;
@ -174,7 +141,6 @@ impl FromStr for SearchQueryCondition {
/// Options associated with a search query
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(default)]
pub struct SearchQueryOptions {
/// Restrict search to only these file types (otherwise all are allowed).
@ -219,16 +185,8 @@ pub struct SearchQueryOptions {
pub pagination: Option<u64>,
}
#[cfg(feature = "schemars")]
impl SearchQueryOptions {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryOptions)
}
}
/// Represents a match for a search query
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")]
pub enum SearchQueryMatch {
/// Matches part of a file's path
@ -254,16 +212,8 @@ impl SearchQueryMatch {
}
}
#[cfg(feature = "schemars")]
impl SearchQueryMatch {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryMatch)
}
}
/// Represents details for a match on a path
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SearchQueryPathMatch {
/// Path associated with the match
pub path: PathBuf,
@ -273,16 +223,8 @@ pub struct SearchQueryPathMatch {
pub submatches: Vec<SearchQuerySubmatch>,
}
#[cfg(feature = "schemars")]
impl SearchQueryPathMatch {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryPathMatch)
}
}
/// Represents details for a match on a file's contents
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SearchQueryContentsMatch {
/// Path to file whose contents match
pub path: PathBuf,
@ -301,15 +243,7 @@ pub struct SearchQueryContentsMatch {
pub submatches: Vec<SearchQuerySubmatch>,
}
#[cfg(feature = "schemars")]
impl SearchQueryContentsMatch {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryContentsMatch)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SearchQuerySubmatch {
/// Content matched by query
pub r#match: SearchQueryMatchData,
@ -321,15 +255,7 @@ pub struct SearchQuerySubmatch {
pub end: u64,
}
#[cfg(feature = "schemars")]
impl SearchQuerySubmatch {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQuerySubmatch)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(
rename_all = "snake_case",
deny_unknown_fields,
@ -373,20 +299,11 @@ impl SearchQueryMatchData {
}
}
#[cfg(feature = "schemars")]
impl SearchQueryMatchData {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SearchQueryMatchData)
}
}
#[cfg(test)]
mod tests {
use super::*;
mod search_query_condition {
use test_log::test;
use super::*;
#[test]

@ -1,11 +1,9 @@
use std::env;
use std::path::PathBuf;
use serde::{Deserialize, Serialize};
/// Represents information about a system
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
pub struct SystemInfo {
/// Family of the operating system as described in
/// https://doc.rust-lang.org/std/env/consts/constant.FAMILY.html
@ -32,28 +30,3 @@ pub struct SystemInfo {
/// Default shell tied to user running the server process
pub shell: String,
}
#[cfg(feature = "schemars")]
impl SystemInfo {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(SystemInfo)
}
}
impl Default for SystemInfo {
fn default() -> Self {
Self {
family: env::consts::FAMILY.to_string(),
os: env::consts::OS.to_string(),
arch: env::consts::ARCH.to_string(),
current_dir: env::current_dir().unwrap_or_default(),
main_separator: std::path::MAIN_SEPARATOR,
username: whoami::username(),
shell: if cfg!(windows) {
env::var("ComSpec").unwrap_or_else(|_| String::from("cmd.exe"))
} else {
env::var("SHELL").unwrap_or_else(|_| String::from("/bin/sh"))
},
}
}
}

@ -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,
}
}
}

@ -1,136 +1,24 @@
use std::io;
use std::collections::HashMap;
use std::path::PathBuf;
use derive_more::{From, IsVariant};
use derive_more::IsVariant;
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString};
mod capabilities;
pub use capabilities::*;
mod change;
pub use change::*;
mod cmd;
pub use cmd::*;
mod error;
pub use error::*;
mod filesystem;
pub use filesystem::*;
mod metadata;
pub use metadata::*;
mod permissions;
pub use permissions::*;
mod pty;
pub use pty::*;
mod search;
pub use search::*;
mod system;
pub use system::*;
mod utils;
pub(crate) use utils::*;
/// Id for a remote process
pub type ProcessId = u32;
use crate::common::{
ChangeKind, Cmd, Permissions, ProcessId, PtySize, SearchId, SearchQuery, SetPermissionsOptions,
};
/// Mapping of environment variables
pub type Environment = distant_net::common::Map;
/// Represents a wrapper around a distant message, supporting single and batch requests
#[derive(Clone, Debug, From, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[serde(untagged)]
pub enum Msg<T> {
Single(T),
Batch(Vec<T>),
}
pub type Environment = HashMap<String, String>;
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,
}
}
}
#[cfg(feature = "schemars")]
impl<T: schemars::JsonSchema> Msg<T> {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Msg<T>)
}
/// Used to provide a default serde value of 1
const fn one() -> usize {
1
}
/// Represents the payload of a request to be performed on the remote machine
#[derive(Clone, Debug, PartialEq, Eq, EnumDiscriminants, IsVariant, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[strum_discriminants(derive(
AsRefStr,
strum::Display,
@ -144,11 +32,7 @@ impl<T: schemars::JsonSchema> Msg<T> {
Serialize,
Deserialize
))]
#[cfg_attr(
feature = "schemars",
strum_discriminants(derive(schemars::JsonSchema))
)]
#[strum_discriminants(name(CapabilityKind))]
#[strum_discriminants(name(RequestKind))]
#[strum_discriminants(strum(serialize_all = "snake_case"))]
#[serde(rename_all = "snake_case", deny_unknown_fields, tag = "type")]
pub enum Request {
@ -180,7 +64,6 @@ pub enum Request {
/// Data for server-side writing of content
#[serde(with = "serde_bytes")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
data: Vec<u8>,
},
@ -203,7 +86,6 @@ pub enum Request {
/// Data for server-side writing of content
#[serde(with = "serde_bytes")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
data: Vec<u8>,
},
@ -413,7 +295,6 @@ pub enum Request {
/// Data to send to a process's stdin pipe
#[serde(with = "serde_bytes")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
data: Vec<u8>,
},
@ -431,142 +312,3 @@ pub enum Request {
#[strum_discriminants(strum(message = "Supports retrieving system information"))]
SystemInfo {},
}
#[cfg(feature = "schemars")]
impl Request {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Request)
}
}
/// Represents the payload of a successful response
#[derive(Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, Serialize, Deserialize)]
#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))]
#[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")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
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")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
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")]
#[cfg_attr(feature = "schemars", schemars(with = "Vec<u8>"))]
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 },
}
#[cfg(feature = "schemars")]
impl Response {
pub fn root_schema() -> schemars::schema::RootSchema {
schemars::schema_for!(Response)
}
}
impl From<io::Error> for Response {
fn from(x: io::Error) -> Self {
Self::Error(Error::from(x))
}
}
/// Used to provide a default serde value of 1
const fn one() -> usize {
1
}

@ -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))
}
}

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
pub(crate) fn deserialize_u128_option<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>
pub fn deserialize_u128_option<'de, D>(deserializer: D) -> Result<Option<u128>, D::Error>
where
D: serde::Deserializer<'de>,
{
@ -15,7 +15,7 @@ where
}
}
pub(crate) fn serialize_u128_option<S: serde::Serializer>(
pub fn serialize_u128_option<S: serde::Serializer>(
val: &Option<u128>,
s: S,
) -> Result<S::Ok, S::Error> {
Loading…
Cancel
Save