use std::future::Future; use std::io; use std::path::PathBuf; use std::pin::Pin; use distant_net::client::Channel; use distant_net::common::Request; use crate::client::{ RemoteCommand, RemoteLspCommand, RemoteLspProcess, RemoteOutput, RemoteProcess, Searcher, Watcher, }; use crate::protocol::{ self, ChangeKindSet, DirEntry, Environment, Error as Failure, Metadata, Permissions, PtySize, SearchId, SearchQuery, SetPermissionsOptions, SystemInfo, Version, }; pub type AsyncReturn<'a, T, E = io::Error> = Pin> + Send + 'a>>; fn mismatched_response() -> io::Error { io::Error::new(io::ErrorKind::Other, "Mismatched response") } /// Provides convenience functions on top of a [`Channel`] pub trait DistantChannelExt { /// Appends to a remote file using the data from a collection of bytes fn append_file( &mut self, path: impl Into, data: impl Into>, ) -> AsyncReturn<'_, ()>; /// Appends to a remote file using the data from a string fn append_file_text( &mut self, path: impl Into, data: impl Into, ) -> AsyncReturn<'_, ()>; /// Copies a remote file or directory from src to dst fn copy(&mut self, src: impl Into, dst: impl Into) -> AsyncReturn<'_, ()>; /// Creates a remote directory, optionally creating all parent components if specified fn create_dir(&mut self, path: impl Into, all: bool) -> AsyncReturn<'_, ()>; fn exists(&mut self, path: impl Into) -> AsyncReturn<'_, bool>; /// Retrieves metadata about a path on a remote machine fn metadata( &mut self, path: impl Into, canonicalize: bool, resolve_file_type: bool, ) -> AsyncReturn<'_, Metadata>; /// Sets permissions for a path on a remote machine fn set_permissions( &mut self, path: impl Into, permissions: Permissions, options: SetPermissionsOptions, ) -> AsyncReturn<'_, ()>; /// Perform a search fn search(&mut self, query: impl Into) -> AsyncReturn<'_, Searcher>; /// Cancel an active search query fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()>; /// Reads entries from a directory, returning a tuple of directory entries and failures fn read_dir( &mut self, path: impl Into, depth: usize, absolute: bool, canonicalize: bool, include_root: bool, ) -> AsyncReturn<'_, (Vec, Vec)>; /// Reads a remote file as a collection of bytes fn read_file(&mut self, path: impl Into) -> AsyncReturn<'_, Vec>; /// Returns a remote file as a string fn read_file_text(&mut self, path: impl Into) -> AsyncReturn<'_, String>; /// Removes a remote file or directory, supporting removal of non-empty directories if /// force is true fn remove(&mut self, path: impl Into, force: bool) -> AsyncReturn<'_, ()>; /// Renames a remote file or directory from src to dst fn rename(&mut self, src: impl Into, dst: impl Into) -> AsyncReturn<'_, ()>; /// Watches a remote file or directory fn watch( &mut self, path: impl Into, recursive: bool, only: impl Into, except: impl Into, ) -> AsyncReturn<'_, Watcher>; /// Unwatches a remote file or directory fn unwatch(&mut self, path: impl Into) -> AsyncReturn<'_, ()>; /// Spawns a process on the remote machine fn spawn( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteProcess>; /// Spawns an LSP process on the remote machine fn spawn_lsp( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteLspProcess>; /// Spawns a process on the remote machine and wait for it to complete fn output( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteOutput>; /// Retrieves information about the remote system fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo>; /// Retrieves server version information fn version(&mut self) -> AsyncReturn<'_, Version>; /// Writes a remote file with the data from a collection of bytes fn write_file( &mut self, path: impl Into, data: impl Into>, ) -> AsyncReturn<'_, ()>; /// Writes a remote file with the data from a string fn write_file_text( &mut self, path: impl Into, data: impl Into, ) -> AsyncReturn<'_, ()>; } macro_rules! make_body { ($self:expr, $data:expr, @ok) => { make_body!($self, $data, |data| { match data { protocol::Response::Ok => Ok(()), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } }) }; ($self:expr, $data:expr, $and_then:expr) => {{ let req = Request::new(protocol::Msg::Single($data)); Box::pin(async move { $self .send(req) .await .and_then(|res| match res.payload { protocol::Msg::Single(x) => Ok(x), _ => Err(mismatched_response()), }) .and_then($and_then) }) }}; } impl DistantChannelExt for Channel, protocol::Msg> { fn append_file( &mut self, path: impl Into, data: impl Into>, ) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::FileAppend { path: path.into(), data: data.into() }, @ok ) } fn append_file_text( &mut self, path: impl Into, data: impl Into, ) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::FileAppendText { path: path.into(), text: data.into() }, @ok ) } fn copy(&mut self, src: impl Into, dst: impl Into) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::Copy { src: src.into(), dst: dst.into() }, @ok ) } fn create_dir(&mut self, path: impl Into, all: bool) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::DirCreate { path: path.into(), all }, @ok ) } fn exists(&mut self, path: impl Into) -> AsyncReturn<'_, bool> { make_body!( self, protocol::Request::Exists { path: path.into() }, |data| match data { protocol::Response::Exists { value } => Ok(value), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } ) } fn metadata( &mut self, path: impl Into, canonicalize: bool, resolve_file_type: bool, ) -> AsyncReturn<'_, Metadata> { make_body!( self, protocol::Request::Metadata { path: path.into(), canonicalize, resolve_file_type }, |data| match data { protocol::Response::Metadata(x) => Ok(x), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } ) } fn set_permissions( &mut self, path: impl Into, permissions: Permissions, options: SetPermissionsOptions, ) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::SetPermissions { path: path.into(), permissions, options, }, @ok ) } fn search(&mut self, query: impl Into) -> AsyncReturn<'_, Searcher> { let query = query.into(); Box::pin(async move { Searcher::search(self.clone(), query).await }) } fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::CancelSearch { id }, @ok ) } fn read_dir( &mut self, path: impl Into, depth: usize, absolute: bool, canonicalize: bool, include_root: bool, ) -> AsyncReturn<'_, (Vec, Vec)> { make_body!( self, protocol::Request::DirRead { path: path.into(), depth, absolute, canonicalize, include_root }, |data| match data { protocol::Response::DirEntries { entries, errors } => Ok((entries, errors)), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } ) } fn read_file(&mut self, path: impl Into) -> AsyncReturn<'_, Vec> { make_body!( self, protocol::Request::FileRead { path: path.into() }, |data| match data { protocol::Response::Blob { data } => Ok(data), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } ) } fn read_file_text(&mut self, path: impl Into) -> AsyncReturn<'_, String> { make_body!( self, protocol::Request::FileReadText { path: path.into() }, |data| match data { protocol::Response::Text { data } => Ok(data), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), } ) } fn remove(&mut self, path: impl Into, force: bool) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::Remove { path: path.into(), force }, @ok ) } fn rename(&mut self, src: impl Into, dst: impl Into) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::Rename { src: src.into(), dst: dst.into() }, @ok ) } fn watch( &mut self, path: impl Into, recursive: bool, only: impl Into, except: impl Into, ) -> AsyncReturn<'_, Watcher> { let path = path.into(); let only = only.into(); let except = except.into(); Box::pin(async move { Watcher::watch(self.clone(), path, recursive, only, except).await }) } fn unwatch(&mut self, path: impl Into) -> AsyncReturn<'_, ()> { fn inner_unwatch( channel: &mut Channel< protocol::Msg, protocol::Msg, >, path: impl Into, ) -> AsyncReturn<'_, ()> { make_body!( channel, protocol::Request::Unwatch { path: path.into() }, @ok ) } let path = path.into(); Box::pin(async move { inner_unwatch(self, path).await }) } fn spawn( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteProcess> { let cmd = cmd.into(); Box::pin(async move { RemoteCommand::new() .environment(environment) .current_dir(current_dir) .pty(pty) .spawn(self.clone(), cmd) .await }) } fn spawn_lsp( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteLspProcess> { let cmd = cmd.into(); Box::pin(async move { RemoteLspCommand::new() .environment(environment) .current_dir(current_dir) .pty(pty) .spawn(self.clone(), cmd) .await }) } fn output( &mut self, cmd: impl Into, environment: Environment, current_dir: Option, pty: Option, ) -> AsyncReturn<'_, RemoteOutput> { let cmd = cmd.into(); Box::pin(async move { RemoteCommand::new() .environment(environment) .current_dir(current_dir) .pty(pty) .spawn(self.clone(), cmd) .await? .output() .await }) } fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo> { make_body!(self, protocol::Request::SystemInfo {}, |data| match data { protocol::Response::SystemInfo(x) => Ok(x), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), }) } fn version(&mut self) -> AsyncReturn<'_, Version> { make_body!(self, protocol::Request::Version {}, |data| match data { protocol::Response::Version(x) => Ok(x), protocol::Response::Error(x) => Err(io::Error::from(x)), _ => Err(mismatched_response()), }) } fn write_file( &mut self, path: impl Into, data: impl Into>, ) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::FileWrite { path: path.into(), data: data.into() }, @ok ) } fn write_file_text( &mut self, path: impl Into, data: impl Into, ) -> AsyncReturn<'_, ()> { make_body!( self, protocol::Request::FileWriteText { path: path.into(), text: data.into() }, @ok ) } }