mirror of https://github.com/chipsenkbeil/distant
More progress....
parent
8e1506f6de
commit
0690de67c5
@ -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 destination;
|
||||||
mod map;
|
mod map;
|
||||||
|
mod msg;
|
||||||
mod stream;
|
mod stream;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
|
||||||
pub use destination::{Destination, Host, HostParseError};
|
pub use destination::{Destination, Host, HostParseError};
|
||||||
pub use map::{Map, MapParseError};
|
pub use map::{Map, MapParseError};
|
||||||
|
pub use msg::{Id, Request, RequestFlags, Response};
|
||||||
pub use stream::Stream;
|
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>,
|
||||||
|
}
|
Loading…
Reference in New Issue