distant_core/client/
ext.rs

1use std::future::Future;
2use std::io;
3use std::path::PathBuf;
4use std::pin::Pin;
5
6use distant_net::client::Channel;
7use distant_net::common::Request;
8
9use crate::client::{
10    RemoteCommand, RemoteLspCommand, RemoteLspProcess, RemoteOutput, RemoteProcess, Searcher,
11    Watcher,
12};
13use crate::protocol::{
14    self, ChangeKindSet, DirEntry, Environment, Error as Failure, Metadata, Permissions, PtySize,
15    SearchId, SearchQuery, SetPermissionsOptions, SystemInfo, Version,
16};
17
18pub type AsyncReturn<'a, T, E = io::Error> =
19    Pin<Box<dyn Future<Output = Result<T, E>> + Send + 'a>>;
20
21fn mismatched_response() -> io::Error {
22    io::Error::new(io::ErrorKind::Other, "Mismatched response")
23}
24
25/// Provides convenience functions on top of a [`Channel`]
26pub trait DistantChannelExt {
27    /// Appends to a remote file using the data from a collection of bytes
28    fn append_file(
29        &mut self,
30        path: impl Into<PathBuf>,
31        data: impl Into<Vec<u8>>,
32    ) -> AsyncReturn<'_, ()>;
33
34    /// Appends to a remote file using the data from a string
35    fn append_file_text(
36        &mut self,
37        path: impl Into<PathBuf>,
38        data: impl Into<String>,
39    ) -> AsyncReturn<'_, ()>;
40
41    /// Copies a remote file or directory from src to dst
42    fn copy(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
43
44    /// Creates a remote directory, optionally creating all parent components if specified
45    fn create_dir(&mut self, path: impl Into<PathBuf>, all: bool) -> AsyncReturn<'_, ()>;
46
47    /// Checks whether the `path` exists on the remote machine
48    fn exists(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, bool>;
49
50    /// Checks whether this client is compatible with the remote server
51    fn is_compatible(&mut self) -> AsyncReturn<'_, bool>;
52
53    /// Retrieves metadata about a path on a remote machine
54    fn metadata(
55        &mut self,
56        path: impl Into<PathBuf>,
57        canonicalize: bool,
58        resolve_file_type: bool,
59    ) -> AsyncReturn<'_, Metadata>;
60
61    /// Sets permissions for a path on a remote machine
62    fn set_permissions(
63        &mut self,
64        path: impl Into<PathBuf>,
65        permissions: Permissions,
66        options: SetPermissionsOptions,
67    ) -> AsyncReturn<'_, ()>;
68
69    /// Perform a search
70    fn search(&mut self, query: impl Into<SearchQuery>) -> AsyncReturn<'_, Searcher>;
71
72    /// Cancel an active search query
73    fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()>;
74
75    /// Reads entries from a directory, returning a tuple of directory entries and failures
76    fn read_dir(
77        &mut self,
78        path: impl Into<PathBuf>,
79        depth: usize,
80        absolute: bool,
81        canonicalize: bool,
82        include_root: bool,
83    ) -> AsyncReturn<'_, (Vec<DirEntry>, Vec<Failure>)>;
84
85    /// Reads a remote file as a collection of bytes
86    fn read_file(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, Vec<u8>>;
87
88    /// Returns a remote file as a string
89    fn read_file_text(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, String>;
90
91    /// Removes a remote file or directory, supporting removal of non-empty directories if
92    /// force is true
93    fn remove(&mut self, path: impl Into<PathBuf>, force: bool) -> AsyncReturn<'_, ()>;
94
95    /// Renames a remote file or directory from src to dst
96    fn rename(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
97
98    /// Watches a remote file or directory
99    fn watch(
100        &mut self,
101        path: impl Into<PathBuf>,
102        recursive: bool,
103        only: impl Into<ChangeKindSet>,
104        except: impl Into<ChangeKindSet>,
105    ) -> AsyncReturn<'_, Watcher>;
106
107    /// Unwatches a remote file or directory
108    fn unwatch(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, ()>;
109
110    /// Spawns a process on the remote machine
111    fn spawn(
112        &mut self,
113        cmd: impl Into<String>,
114        environment: Environment,
115        current_dir: Option<PathBuf>,
116        pty: Option<PtySize>,
117    ) -> AsyncReturn<'_, RemoteProcess>;
118
119    /// Spawns an LSP process on the remote machine
120    fn spawn_lsp(
121        &mut self,
122        cmd: impl Into<String>,
123        environment: Environment,
124        current_dir: Option<PathBuf>,
125        pty: Option<PtySize>,
126    ) -> AsyncReturn<'_, RemoteLspProcess>;
127
128    /// Spawns a process on the remote machine and wait for it to complete
129    fn output(
130        &mut self,
131        cmd: impl Into<String>,
132        environment: Environment,
133        current_dir: Option<PathBuf>,
134        pty: Option<PtySize>,
135    ) -> AsyncReturn<'_, RemoteOutput>;
136
137    /// Retrieves information about the remote system
138    fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo>;
139
140    /// Retrieves server version information
141    fn version(&mut self) -> AsyncReturn<'_, Version>;
142
143    /// Returns version of protocol that the client uses
144    fn protocol_version(&self) -> protocol::semver::Version;
145
146    /// Writes a remote file with the data from a collection of bytes
147    fn write_file(
148        &mut self,
149        path: impl Into<PathBuf>,
150        data: impl Into<Vec<u8>>,
151    ) -> AsyncReturn<'_, ()>;
152
153    /// Writes a remote file with the data from a string
154    fn write_file_text(
155        &mut self,
156        path: impl Into<PathBuf>,
157        data: impl Into<String>,
158    ) -> AsyncReturn<'_, ()>;
159}
160
161macro_rules! make_body {
162    ($self:expr, $data:expr, @ok) => {
163        make_body!($self, $data, |data| {
164            match data {
165                protocol::Response::Ok => Ok(()),
166                protocol::Response::Error(x) => Err(io::Error::from(x)),
167                _ => Err(mismatched_response()),
168            }
169        })
170    };
171
172    ($self:expr, $data:expr, $and_then:expr) => {{
173        let req = Request::new(protocol::Msg::Single($data));
174        Box::pin(async move {
175            $self
176                .send(req)
177                .await
178                .and_then(|res| match res.payload {
179                    protocol::Msg::Single(x) => Ok(x),
180                    _ => Err(mismatched_response()),
181                })
182                .and_then($and_then)
183        })
184    }};
185}
186
187impl DistantChannelExt
188    for Channel<protocol::Msg<protocol::Request>, protocol::Msg<protocol::Response>>
189{
190    fn append_file(
191        &mut self,
192        path: impl Into<PathBuf>,
193        data: impl Into<Vec<u8>>,
194    ) -> AsyncReturn<'_, ()> {
195        make_body!(
196            self,
197            protocol::Request::FileAppend { path: path.into(), data: data.into() },
198            @ok
199        )
200    }
201
202    fn append_file_text(
203        &mut self,
204        path: impl Into<PathBuf>,
205        data: impl Into<String>,
206    ) -> AsyncReturn<'_, ()> {
207        make_body!(
208            self,
209            protocol::Request::FileAppendText { path: path.into(), text: data.into() },
210            @ok
211        )
212    }
213
214    fn copy(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
215        make_body!(
216            self,
217            protocol::Request::Copy { src: src.into(), dst: dst.into() },
218            @ok
219        )
220    }
221
222    fn create_dir(&mut self, path: impl Into<PathBuf>, all: bool) -> AsyncReturn<'_, ()> {
223        make_body!(
224            self,
225            protocol::Request::DirCreate { path: path.into(), all },
226            @ok
227        )
228    }
229
230    fn exists(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, bool> {
231        make_body!(
232            self,
233            protocol::Request::Exists { path: path.into() },
234            |data| match data {
235                protocol::Response::Exists { value } => Ok(value),
236                protocol::Response::Error(x) => Err(io::Error::from(x)),
237                _ => Err(mismatched_response()),
238            }
239        )
240    }
241
242    fn is_compatible(&mut self) -> AsyncReturn<'_, bool> {
243        make_body!(self, protocol::Request::Version {}, |data| match data {
244            protocol::Response::Version(version) =>
245                Ok(protocol::is_compatible_with(&version.protocol_version)),
246            protocol::Response::Error(x) => Err(io::Error::from(x)),
247            _ => Err(mismatched_response()),
248        })
249    }
250
251    fn metadata(
252        &mut self,
253        path: impl Into<PathBuf>,
254        canonicalize: bool,
255        resolve_file_type: bool,
256    ) -> AsyncReturn<'_, Metadata> {
257        make_body!(
258            self,
259            protocol::Request::Metadata {
260                path: path.into(),
261                canonicalize,
262                resolve_file_type
263            },
264            |data| match data {
265                protocol::Response::Metadata(x) => Ok(x),
266                protocol::Response::Error(x) => Err(io::Error::from(x)),
267                _ => Err(mismatched_response()),
268            }
269        )
270    }
271
272    fn set_permissions(
273        &mut self,
274        path: impl Into<PathBuf>,
275        permissions: Permissions,
276        options: SetPermissionsOptions,
277    ) -> AsyncReturn<'_, ()> {
278        make_body!(
279            self,
280            protocol::Request::SetPermissions {
281                path: path.into(),
282                permissions,
283                options,
284            },
285            @ok
286        )
287    }
288
289    fn search(&mut self, query: impl Into<SearchQuery>) -> AsyncReturn<'_, Searcher> {
290        let query = query.into();
291        Box::pin(async move { Searcher::search(self.clone(), query).await })
292    }
293
294    fn cancel_search(&mut self, id: SearchId) -> AsyncReturn<'_, ()> {
295        make_body!(
296            self,
297            protocol::Request::CancelSearch { id },
298            @ok
299        )
300    }
301
302    fn read_dir(
303        &mut self,
304        path: impl Into<PathBuf>,
305        depth: usize,
306        absolute: bool,
307        canonicalize: bool,
308        include_root: bool,
309    ) -> AsyncReturn<'_, (Vec<DirEntry>, Vec<Failure>)> {
310        make_body!(
311            self,
312            protocol::Request::DirRead {
313                path: path.into(),
314                depth,
315                absolute,
316                canonicalize,
317                include_root
318            },
319            |data| match data {
320                protocol::Response::DirEntries { entries, errors } => Ok((entries, errors)),
321                protocol::Response::Error(x) => Err(io::Error::from(x)),
322                _ => Err(mismatched_response()),
323            }
324        )
325    }
326
327    fn read_file(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, Vec<u8>> {
328        make_body!(
329            self,
330            protocol::Request::FileRead { path: path.into() },
331            |data| match data {
332                protocol::Response::Blob { data } => Ok(data),
333                protocol::Response::Error(x) => Err(io::Error::from(x)),
334                _ => Err(mismatched_response()),
335            }
336        )
337    }
338
339    fn read_file_text(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, String> {
340        make_body!(
341            self,
342            protocol::Request::FileReadText { path: path.into() },
343            |data| match data {
344                protocol::Response::Text { data } => Ok(data),
345                protocol::Response::Error(x) => Err(io::Error::from(x)),
346                _ => Err(mismatched_response()),
347            }
348        )
349    }
350
351    fn remove(&mut self, path: impl Into<PathBuf>, force: bool) -> AsyncReturn<'_, ()> {
352        make_body!(
353            self,
354            protocol::Request::Remove { path: path.into(), force },
355            @ok
356        )
357    }
358
359    fn rename(&mut self, src: impl Into<PathBuf>, dst: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
360        make_body!(
361            self,
362            protocol::Request::Rename { src: src.into(), dst: dst.into() },
363            @ok
364        )
365    }
366
367    fn watch(
368        &mut self,
369        path: impl Into<PathBuf>,
370        recursive: bool,
371        only: impl Into<ChangeKindSet>,
372        except: impl Into<ChangeKindSet>,
373    ) -> AsyncReturn<'_, Watcher> {
374        let path = path.into();
375        let only = only.into();
376        let except = except.into();
377        Box::pin(async move { Watcher::watch(self.clone(), path, recursive, only, except).await })
378    }
379
380    fn unwatch(&mut self, path: impl Into<PathBuf>) -> AsyncReturn<'_, ()> {
381        fn inner_unwatch(
382            channel: &mut Channel<
383                protocol::Msg<protocol::Request>,
384                protocol::Msg<protocol::Response>,
385            >,
386            path: impl Into<PathBuf>,
387        ) -> AsyncReturn<'_, ()> {
388            make_body!(
389                channel,
390                protocol::Request::Unwatch { path: path.into() },
391                @ok
392            )
393        }
394
395        let path = path.into();
396
397        Box::pin(async move { inner_unwatch(self, path).await })
398    }
399
400    fn spawn(
401        &mut self,
402        cmd: impl Into<String>,
403        environment: Environment,
404        current_dir: Option<PathBuf>,
405        pty: Option<PtySize>,
406    ) -> AsyncReturn<'_, RemoteProcess> {
407        let cmd = cmd.into();
408        Box::pin(async move {
409            RemoteCommand::new()
410                .environment(environment)
411                .current_dir(current_dir)
412                .pty(pty)
413                .spawn(self.clone(), cmd)
414                .await
415        })
416    }
417
418    fn spawn_lsp(
419        &mut self,
420        cmd: impl Into<String>,
421        environment: Environment,
422        current_dir: Option<PathBuf>,
423        pty: Option<PtySize>,
424    ) -> AsyncReturn<'_, RemoteLspProcess> {
425        let cmd = cmd.into();
426        Box::pin(async move {
427            RemoteLspCommand::new()
428                .environment(environment)
429                .current_dir(current_dir)
430                .pty(pty)
431                .spawn(self.clone(), cmd)
432                .await
433        })
434    }
435
436    fn output(
437        &mut self,
438        cmd: impl Into<String>,
439        environment: Environment,
440        current_dir: Option<PathBuf>,
441        pty: Option<PtySize>,
442    ) -> AsyncReturn<'_, RemoteOutput> {
443        let cmd = cmd.into();
444        Box::pin(async move {
445            RemoteCommand::new()
446                .environment(environment)
447                .current_dir(current_dir)
448                .pty(pty)
449                .spawn(self.clone(), cmd)
450                .await?
451                .output()
452                .await
453        })
454    }
455
456    fn system_info(&mut self) -> AsyncReturn<'_, SystemInfo> {
457        make_body!(self, protocol::Request::SystemInfo {}, |data| match data {
458            protocol::Response::SystemInfo(x) => Ok(x),
459            protocol::Response::Error(x) => Err(io::Error::from(x)),
460            _ => Err(mismatched_response()),
461        })
462    }
463
464    fn version(&mut self) -> AsyncReturn<'_, Version> {
465        make_body!(self, protocol::Request::Version {}, |data| match data {
466            protocol::Response::Version(x) => Ok(x),
467            protocol::Response::Error(x) => Err(io::Error::from(x)),
468            _ => Err(mismatched_response()),
469        })
470    }
471
472    fn protocol_version(&self) -> protocol::semver::Version {
473        protocol::PROTOCOL_VERSION
474    }
475
476    fn write_file(
477        &mut self,
478        path: impl Into<PathBuf>,
479        data: impl Into<Vec<u8>>,
480    ) -> AsyncReturn<'_, ()> {
481        make_body!(
482            self,
483            protocol::Request::FileWrite { path: path.into(), data: data.into() },
484            @ok
485        )
486    }
487
488    fn write_file_text(
489        &mut self,
490        path: impl Into<PathBuf>,
491        data: impl Into<String>,
492    ) -> AsyncReturn<'_, ()> {
493        make_body!(
494            self,
495            protocol::Request::FileWriteText { path: path.into(), text: data.into() },
496            @ok
497        )
498    }
499}