More progress....

feat/RusshSupport
Chip Senkbeil 6 months ago
parent 8e1506f6de
commit 0690de67c5
No known key found for this signature in database
GPG Key ID: 35EF1F8EC72A4131

30
Cargo.lock generated

@ -842,7 +842,7 @@ dependencies = [
"serde",
"serde_bytes",
"serde_json",
"strum",
"strum 0.24.1",
"test-log",
"tokio",
]
@ -914,7 +914,7 @@ dependencies = [
"serde_bytes",
"serde_json",
"sha2 0.10.6",
"strum",
"strum 0.24.1",
"tempfile",
"test-log",
"tokio",
@ -934,7 +934,7 @@ dependencies = [
"serde",
"serde_bytes",
"serde_json",
"strum",
"strum 0.25.0",
]
[[package]]
@ -3221,7 +3221,16 @@ version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f"
dependencies = [
"strum_macros",
"strum_macros 0.24.3",
]
[[package]]
name = "strum"
version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125"
dependencies = [
"strum_macros 0.25.3",
]
[[package]]
@ -3237,6 +3246,19 @@ dependencies = [
"syn 1.0.109",
]
[[package]]
name = "strum_macros"
version = "0.25.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0"
dependencies = [
"heck",
"proc-macro2",
"quote",
"rustversion",
"syn 2.0.36",
]
[[package]]
name = "subtle"
version = "2.4.1"

@ -23,7 +23,7 @@ regex = "1.8.3"
semver = { version = "1.0.17", features = ["serde"] }
serde = { version = "1.0.163", features = ["derive"] }
serde_bytes = "0.11.9"
strum = { version = "0.24.1", features = ["derive"] }
strum = { version = "0.25.0", features = ["derive"] }
[dev-dependencies]
rmp = "0.8.11"

@ -2,7 +2,7 @@ use std::io;
use derive_more::IsVariant;
use serde::{Deserialize, Serialize};
use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString};
use strum::{AsRefStr, EnumDiscriminants, EnumIter, EnumMessage, EnumString, IntoStaticStr};
use crate::common::{
Change, DirEntry, Error, Metadata, ProcessId, SearchId, SearchQueryMatch, SystemInfo, Version,
@ -10,7 +10,16 @@ use crate::common::{
/// Represents the payload of a successful response
#[derive(
Clone, Debug, PartialEq, Eq, AsRefStr, IsVariant, EnumDiscriminants, Serialize, Deserialize,
Clone,
Debug,
PartialEq,
Eq,
AsRefStr,
IsVariant,
EnumDiscriminants,
Serialize,
Deserialize,
IntoStaticStr,
)]
#[strum_discriminants(derive(
AsRefStr,
@ -21,6 +30,7 @@ use crate::common::{
Hash,
PartialOrd,
Ord,
IntoStaticStr,
IsVariant,
Serialize,
Deserialize

@ -1,7 +1,8 @@
use std::io;
use async_trait::async_trait;
use distant_core_protocol::Response;
use crate::protocol;
/// Represents a context associated when an API request is being executed, supporting the ability
/// to send responses back asynchronously.
@ -14,5 +15,5 @@ pub trait Ctx: Send {
fn clone_ctx(&self) -> Box<dyn Ctx>;
/// Sends some response back.
fn send(&self, response: Response) -> io::Result<()>;
fn send(&self, msg: protocol::Msg<protocol::Response>) -> io::Result<()>;
}

@ -2,13 +2,19 @@ use std::io;
use std::sync::Arc;
use async_trait::async_trait;
use distant_core_protocol::{Error, Request, Response};
use tokio::sync::mpsc;
use crate::api::{
Api, Ctx, FileSystemApi, ProcessApi, SearchApi, SystemInfoApi, VersionApi, WatchApi,
};
use crate::common::Stream;
use crate::common::{Request, Response, Stream};
use crate::protocol;
mod err;
mod ext;
pub use err::{ClientError, ClientResult};
pub use ext::ClientExt;
/// Full API for a distant-compatible client.
#[async_trait]
@ -66,9 +72,9 @@ impl<T: Api + 'static> Client for ClientBridge<T> {
Box::new(__Ctx(self.0, self.1.clone()))
}
fn send(&self, response: Response) -> io::Result<()> {
fn send(&self, msg: protocol::Msg<protocol::Response>) -> io::Result<()> {
self.1
.send(response)
.send(msg)
.map_err(|_| io::Error::new(io::ErrorKind::Other, "Bridge has closed"))
}
}
@ -95,62 +101,102 @@ impl<T: Api + 'static> Client for ClientBridge<T> {
}
}
/// Processes an incoming request.
async fn handle_request<T>(api: Arc<T>, ctx: Box<dyn Ctx>, request: Request) -> Response
where
T: Api,
{
let origin = request.id;
let sequence = request.flags.sequence;
Response {
id: rand::random(),
origin,
payload: match request.payload {
protocol::Msg::Single(request) => {
protocol::Msg::Single(handle_protocol_request(api, ctx, request).await)
}
protocol::Msg::Batch(requests) if sequence => {
let mut responses = Vec::new();
for request in requests {
responses.push(
handle_protocol_request(Arc::clone(&api), ctx.clone_ctx(), request).await,
);
}
protocol::Msg::Batch(responses)
}
protocol::Msg::Batch(requests) => {
let mut responses = Vec::new();
for request in requests {
responses.push(
handle_protocol_request(Arc::clone(&api), ctx.clone_ctx(), request).await,
);
}
protocol::Msg::Batch(responses)
}
},
}
}
/// Processes a singular protocol request using the provided api and ctx.
async fn handle_protocol_request<T>(
api: Arc<T>,
ctx: Box<dyn Ctx>,
request: protocol::Request,
) -> protocol::Response
where
T: Api,
{
match request {
Request::Version {} => {
protocol::Request::Version {} => {
let api = api.version();
api.version(ctx)
.await
.map(Response::Version)
.unwrap_or_else(Response::from)
.map(protocol::Response::Version)
.unwrap_or_else(protocol::Response::from)
}
Request::FileRead { path } => {
protocol::Request::FileRead { path } => {
let api = api.file_system();
api.read_file(ctx, path)
.await
.map(|data| Response::Blob { data })
.unwrap_or_else(Response::from)
.map(|data| protocol::Response::Blob { data })
.unwrap_or_else(protocol::Response::from)
}
Request::FileReadText { path } => {
protocol::Request::FileReadText { path } => {
let api = api.file_system();
api.read_file_text(ctx, path)
.await
.map(|data| Response::Text { data })
.unwrap_or_else(Response::from)
.map(|data| protocol::Response::Text { data })
.unwrap_or_else(protocol::Response::from)
}
Request::FileWrite { path, data } => {
protocol::Request::FileWrite { path, data } => {
let api = api.file_system();
api.write_file(ctx, path, data)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::FileWriteText { path, text } => {
protocol::Request::FileWriteText { path, text } => {
let api = api.file_system();
api.write_file_text(ctx, path, text)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::FileAppend { path, data } => {
protocol::Request::FileAppend { path, data } => {
let api = api.file_system();
api.append_file(ctx, path, data)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::FileAppendText { path, text } => {
protocol::Request::FileAppendText { path, text } => {
let api = api.file_system();
api.append_file_text(ctx, path, text)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::DirRead {
protocol::Request::DirRead {
path,
depth,
absolute,
@ -160,41 +206,41 @@ where
let api = api.file_system();
api.read_dir(ctx, path, depth, absolute, canonicalize, include_root)
.await
.map(|(entries, errors)| Response::DirEntries {
.map(|(entries, errors)| protocol::Response::DirEntries {
entries,
errors: errors.into_iter().map(Error::from).collect(),
errors: errors.into_iter().map(protocol::Error::from).collect(),
})
.unwrap_or_else(Response::from)
.unwrap_or_else(protocol::Response::from)
}
Request::DirCreate { path, all } => {
protocol::Request::DirCreate { path, all } => {
let api = api.file_system();
api.create_dir(ctx, path, all)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Remove { path, force } => {
protocol::Request::Remove { path, force } => {
let api = api.file_system();
api.remove(ctx, path, force)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Copy { src, dst } => {
protocol::Request::Copy { src, dst } => {
let api = api.file_system();
api.copy(ctx, src, dst)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Rename { src, dst } => {
protocol::Request::Rename { src, dst } => {
let api = api.file_system();
api.rename(ctx, src, dst)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Watch {
protocol::Request::Watch {
path,
recursive,
only,
@ -203,24 +249,24 @@ where
let api = api.watch();
api.watch(ctx, path, recursive, only, except)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Unwatch { path } => {
protocol::Request::Unwatch { path } => {
let api = api.watch();
api.unwatch(ctx, path)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Exists { path } => {
protocol::Request::Exists { path } => {
let api = api.file_system();
api.exists(ctx, path)
.await
.map(|value| Response::Exists { value })
.unwrap_or_else(Response::from)
.map(|value| protocol::Response::Exists { value })
.unwrap_or_else(protocol::Response::from)
}
Request::Metadata {
protocol::Request::Metadata {
path,
canonicalize,
resolve_file_type,
@ -228,10 +274,10 @@ where
let api = api.file_system();
api.metadata(ctx, path, canonicalize, resolve_file_type)
.await
.map(Response::Metadata)
.unwrap_or_else(Response::from)
.map(protocol::Response::Metadata)
.unwrap_or_else(protocol::Response::from)
}
Request::SetPermissions {
protocol::Request::SetPermissions {
path,
permissions,
options,
@ -239,24 +285,24 @@ where
let api = api.file_system();
api.set_permissions(ctx, path, permissions, options)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::Search { query } => {
protocol::Request::Search { query } => {
let api = api.search();
api.search(ctx, query)
.await
.map(|id| Response::SearchStarted { id })
.unwrap_or_else(Response::from)
.map(|id| protocol::Response::SearchStarted { id })
.unwrap_or_else(protocol::Response::from)
}
Request::CancelSearch { id } => {
protocol::Request::CancelSearch { id } => {
let api = api.search();
api.cancel_search(ctx, id)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::ProcSpawn {
protocol::Request::ProcSpawn {
cmd,
environment,
current_dir,
@ -265,36 +311,36 @@ where
let api = api.process();
api.proc_spawn(ctx, cmd.into(), environment, current_dir, pty)
.await
.map(|id| Response::ProcSpawned { id })
.unwrap_or_else(Response::from)
.map(|id| protocol::Response::ProcSpawned { id })
.unwrap_or_else(protocol::Response::from)
}
Request::ProcKill { id } => {
protocol::Request::ProcKill { id } => {
let api = api.process();
api.proc_kill(ctx, id)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::ProcStdin { id, data } => {
protocol::Request::ProcStdin { id, data } => {
let api = api.process();
api.proc_stdin(ctx, id, data)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::ProcResizePty { id, size } => {
protocol::Request::ProcResizePty { id, size } => {
let api = api.process();
api.proc_resize_pty(ctx, id, size)
.await
.map(|_| Response::Ok)
.unwrap_or_else(Response::from)
.map(|_| protocol::Response::Ok)
.unwrap_or_else(protocol::Response::from)
}
Request::SystemInfo {} => {
protocol::Request::SystemInfo {} => {
let api = api.system_info();
api.system_info(ctx)
.await
.map(Response::SystemInfo)
.unwrap_or_else(Response::from)
.map(protocol::Response::SystemInfo)
.unwrap_or_else(protocol::Response::from)
}
}
}

@ -0,0 +1,75 @@
use std::error;
use std::fmt;
use std::io;
use crate::common::Id;
use crate::protocol;
pub type ClientResult<T> = Result<T, ClientError>;
/// Errors that can occur from sending data using a client.
#[derive(Debug)]
pub enum ClientError {
/// A networking error occurred when trying to submit the request.
Io(io::Error),
/// An error occurred server-side.
Server(protocol::Error),
/// A response was received, but its origin did not match the request.
WrongOrigin { expected: Id, actual: Id },
/// A response was received, but the payload was single when expected batch or vice versa.
WrongPayloadFormat,
/// A response was received, but its payload type did not match any expected response type.
WrongPayloadType {
expected: &'static [&'static str],
actual: &'static str,
},
}
impl error::Error for ClientError {}
impl fmt::Display for ClientError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Io(x) => fmt::Display::fmt(x, f),
Self::Server(x) => fmt::Display::fmt(x, f),
Self::WrongOrigin { expected, actual } => {
write!(
f,
"Wrong response origin! Expected {expected}, got {actual}."
)
}
Self::WrongPayloadFormat => write!(f, "Wrong response payload format!"),
Self::WrongPayloadType { expected, actual } => {
if expected.len() == 1 {
let expected = expected[0];
write!(
f,
"Wrong response payload type! Wanted {expected}, but got {actual}."
)
} else {
let expected = expected.join(",");
write!(
f,
"Wrong response type! Wanted one of {expected}, but got {actual}."
)
}
}
}
}
}
impl From<io::Error> for ClientError {
fn from(x: io::Error) -> Self {
Self::Io(x)
}
}
impl From<protocol::Error> for ClientError {
fn from(x: protocol::Error) -> Self {
Self::Server(x)
}
}

@ -0,0 +1,228 @@
use async_trait::async_trait;
use std::io;
use std::path::PathBuf;
use super::{Client, ClientError, ClientResult};
use crate::common::{Request, RequestFlags, Response};
use crate::protocol;
macro_rules! impl_client_fn {
($this:ident, $res:expr, $payload:expr) => {{
let id = rand::random();
let request = Request {
id,
flags: RequestFlags {
sequence: false,
..Default::default()
},
payload: protocol::Msg::Single($payload),
};
let response = $this.ask(request).await?;
if response.origin != id {
return Err(ClientError::WrongOrigin {
expected: id,
actual: response.origin,
});
}
match response.payload {
protocol::Msg::Single(protocol::Response::Error(x)) => Err(ClientError::Server(x)),
protocol::Msg::Single(x) if protocol::ResponseKind::from(&x) == $res => Ok(()),
protocol::Msg::Single(x) => Err(ClientError::WrongPayloadType {
expected: &[$res.into()],
actual: x.into(),
}),
protocol::Msg::Batch(_) => Err(ClientError::WrongPayloadFormat),
}
}};
}
/// Provides convenience functions on top of a [`Client`].
#[async_trait]
pub trait ClientExt: Client {
/// Appends to a remote file using the data from a collection of bytes.
async fn append_file(
&mut self,
path: impl Into<PathBuf> + Send,
data: impl Into<Vec<u8>> + Send,
) -> ClientResult<()> {
impl_client_fn!(
self,
protocol::ResponseKind::Ok,
protocol::Request::FileAppend {
path: path.into(),
data: data.into(),
}
)
}
/// Appends to a remote file using the data from a string.
async fn append_file_text(
&mut self,
path: impl Into<PathBuf> + Send,
text: impl Into<String> + Send,
) -> ClientResult<()> {
impl_client_fn!(
self,
protocol::ResponseKind::Ok,
protocol::Request::FileAppendText {
path: path.into(),
text: text.into(),
}
)
}
/// Copies a remote file or directory from src to dst.
async fn copy(
&mut self,
src: impl Into<PathBuf> + Send,
dst: impl Into<PathBuf> + Send,
) -> ClientResult<()> {
impl_client_fn!(
self,
protocol::ResponseKind::Ok,
protocol::Request::Copy {
src: src.into(),
dst: dst.into(),
}
)
}
/// Creates a remote directory, optionally creating all parent components if specified.
async fn create_dir(&mut self, path: impl Into<PathBuf> + Send, all: bool) -> ClientResult<()> {
impl_client_fn!(
self,
protocol::ResponseKind::Ok,
protocol::Request::DirCreate {
path: path.into(),
all,
}
)
}
/// Checks whether the `path` exists on the remote machine.
async fn exists(&mut self, path: impl Into<PathBuf>) -> ClientResult<bool> {
impl_client_fn!(
self,
protocol::ResponseKind::Exists,
protocol::Request::Exists { path: path.into() }
)
}
/// Checks whether this client is compatible with the remote server.
async fn is_compatible(&mut self) -> io::Result<bool>;
/// Retrieves metadata about a path on a remote machine.
async fn metadata(
&mut self,
path: impl Into<PathBuf>,
canonicalize: bool,
resolve_file_type: bool,
) -> io::Result<protocol::Metadata>;
/// Sets permissions for a path on a remote machine.
async fn set_permissions(
&mut self,
path: impl Into<PathBuf>,
permissions: protocol::Permissions,
options: protocol::SetPermissionsOptions,
) -> io::Result<()>;
/// Perform a search.
async fn search(
&mut self,
query: impl Into<protocol::SearchQuery>,
) -> io::Result<protocol::Searcher>;
/// Cancel an active search query.
async fn cancel_search(&mut self, id: protocol::SearchId) -> io::Result<()>;
/// Reads entries from a directory, returning a tuple of directory entries and errors.
async fn read_dir(
&mut self,
path: impl Into<PathBuf>,
depth: usize,
absolute: bool,
canonicalize: bool,
include_root: bool,
) -> io::Result<(Vec<protocol::DirEntry>, Vec<protocol::Error>)>;
/// Reads a remote file as a collection of bytes.
async fn read_file(&mut self, path: impl Into<PathBuf>) -> io::Result<Vec<u8>>;
/// Returns a remote file as a string.
async fn read_file_text(&mut self, path: impl Into<PathBuf>) -> io::Result<String>;
/// Removes a remote file or directory, supporting removal of non-empty directories if
/// force is true.
async fn remove(&mut self, path: impl Into<PathBuf>, force: bool) -> io::Result<()>;
/// Renames a remote file or directory from src to dst.
async fn rename(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> io::Result<()>;
/// Watches a remote file or directory.
async fn watch(
&mut self,
path: impl Into<PathBuf>,
recursive: bool,
only: impl Into<protocol::ChangeKindSet>,
except: impl Into<protocol::ChangeKindSet>,
) -> io::Result<Watcher>;
/// Unwatches a remote file or directory.
async fn unwatch(&mut self, path: impl Into<PathBuf>) -> io::Result<()>;
/// Spawns a process on the remote machine.
async fn spawn(
&mut self,
cmd: impl Into<String>,
environment: Environment,
current_dir: Option<PathBuf>,
pty: Option<protocol::PtySize>,
) -> io::Result<RemoteProcess>;
/// Spawns an LSP process on the remote machine.
async fn spawn_lsp(
&mut self,
cmd: impl Into<String>,
environment: Environment,
current_dir: Option<PathBuf>,
pty: Option<protocol::PtySize>,
) -> io::Result<RemoteLspProcess>;
/// Spawns a process on the remote machine and wait for it to complete.
async fn output(
&mut self,
cmd: impl Into<String>,
environment: Environment,
current_dir: Option<PathBuf>,
pty: Option<protocol::PtySize>,
) -> io::Result<RemoteOutput>;
/// Retrieves information about the remote system.
async fn system_info(&mut self) -> io::Result<protocol::SystemInfo>;
/// Retrieves server version information.
async fn version(&mut self) -> io::Result<protocol::Version>;
/// Returns version of protocol that the client uses.
async fn protocol_version(&self) -> protocol::semver::Version;
/// Writes a remote file with the data from a collection of bytes.
async fn write_file(
&mut self,
path: impl Into<PathBuf>,
data: impl Into<Vec<u8>>,
) -> io::Result<()>;
/// Writes a remote file with the data from a string.
async fn write_file_text(
&mut self,
path: impl Into<PathBuf>,
data: impl Into<String>,
) -> io::Result<()>;
}
impl<T: Client + ?Sized> ClientExt for T {}

@ -1,8 +1,10 @@
mod destination;
mod map;
mod msg;
mod stream;
mod utils;
pub use destination::{Destination, Host, HostParseError};
pub use map::{Map, MapParseError};
pub use msg::{Id, Request, RequestFlags, Response};
pub use stream::Stream;

@ -0,0 +1,37 @@
use serde::{Deserialize, Serialize};
use crate::protocol;
/// Represents an id associated with a request or response.
pub type Id = u64;
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Request {
pub id: Id,
pub flags: RequestFlags,
pub payload: protocol::Msg<protocol::Request>,
}
impl From<protocol::Msg<protocol::Request>> for Request {
fn from(msg: protocol::Msg<protocol::Request>) -> Self {
Self {
id: rand::random(),
flags: Default::default(),
payload: msg,
}
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
pub struct RequestFlags {
/// If true, payload should be executed in sequence; otherwise,
/// a batch payload can be executed in any order.
pub sequence: bool,
}
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub struct Response {
pub id: Id,
pub origin: Id,
pub payload: protocol::Msg<protocol::Response>,
}

@ -112,7 +112,8 @@ impl PluginRegistry {
impl std::fmt::Debug for PluginRegistry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("PluginHandlerRegistry")
f.debug_struct("PluginRegistry")
.field("loaded", &self.loaded)
.field("launch_handlers", &self.launch_handlers.keys())
.field("connect_handlers", &self.connect_handlers.keys())
.finish()

Loading…
Cancel
Save