From 285ee190c406ca056b54309d65932470ee0685ca Mon Sep 17 00:00:00 2001 From: Chip Senkbeil Date: Sun, 29 Oct 2023 12:24:00 -0500 Subject: [PATCH] Flesh out plugin api for server --- distant-core-net/src/common.rs | 4 - distant-plugin/src/api.rs | 432 ++++++++++++++++++++++++++++++++- distant-plugin/src/api/ctx.rs | 9 + 3 files changed, 433 insertions(+), 12 deletions(-) create mode 100644 distant-plugin/src/api/ctx.rs diff --git a/distant-core-net/src/common.rs b/distant-core-net/src/common.rs index 2f24ba4..d1f69ff 100644 --- a/distant-core-net/src/common.rs +++ b/distant-core-net/src/common.rs @@ -1,10 +1,8 @@ mod any; mod connection; -mod destination; mod key; mod keychain; mod listener; -mod map; mod packet; mod port; mod transport; @@ -14,11 +12,9 @@ mod version; pub use any::*; pub(crate) use connection::Connection; pub use connection::ConnectionId; -pub use destination::*; pub use key::*; pub use keychain::*; pub use listener::*; -pub use map::*; pub use packet::*; pub use port::*; pub use transport::*; diff --git a/distant-plugin/src/api.rs b/distant-plugin/src/api.rs index 5b2b68c..1eec124 100644 --- a/distant-plugin/src/api.rs +++ b/distant-plugin/src/api.rs @@ -1,29 +1,445 @@ +use std::any::TypeId; +use std::io; +use std::path::PathBuf; + +use async_trait::async_trait; +use distant_core_protocol::*; + +mod ctx; +pub use ctx::*; + /// Full API that represents a distant-compatible server. +#[async_trait] pub trait Api { type FileSystem: FileSystemApi; type Process: ProcessApi; type Search: SearchApi; type SystemInfo: SystemInfoApi; type Version: VersionApi; + type Watch: WatchApi; + + /// Returns true if [`FileSystemApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_file_system_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } + + /// Returns true if [`ProcessApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_process_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } + + /// Returns true if [`SearchApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_search_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } + + /// Returns true if [`SystemInfoApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_system_info_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } + + /// Returns true if [`VersionApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_version_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } + + /// Returns true if [`WatchApi`] is supported. This is checked by ensuring that the + /// implementation of the associated trait is not [`Unsupported`]. + fn is_watch_api_supported() -> bool { + TypeId::of::() != TypeId::of::() + } } /// API supporting filesystem operations. -pub trait FileSystemApi {} +#[async_trait] +pub trait FileSystemApi { + /// Reads bytes from a file. + /// + /// * `path` - the path to the file + async fn read_file(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result>; + + /// Reads bytes from a file as text. + /// + /// * `path` - the path to the file + async fn read_file_text(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result; + + /// Writes bytes to a file, overwriting the file if it exists. + /// + /// * `path` - the path to the file + /// * `data` - the data to write + async fn write_file(&self, ctx: BoxedCtx, path: PathBuf, data: Vec) -> io::Result<()>; + + /// Writes text to a file, overwriting the file if it exists. + /// + /// * `path` - the path to the file + /// * `data` - the data to write + async fn write_file_text(&self, ctx: BoxedCtx, path: PathBuf, data: String) -> io::Result<()>; + + /// Writes bytes to the end of a file, creating it if it is missing. + /// + /// * `path` - the path to the file + /// * `data` - the data to append + async fn append_file(&self, ctx: BoxedCtx, path: PathBuf, data: Vec) -> io::Result<()>; + + /// Writes bytes to the end of a file, creating it if it is missing. + /// + /// * `path` - the path to the file + /// * `data` - the data to append + async fn append_file_text(&self, ctx: BoxedCtx, path: PathBuf, data: String) -> io::Result<()>; + + /// Reads entries from a directory. + /// + /// * `path` - the path to the directory + /// * `depth` - how far to traverse the directory, 0 being unlimited + /// * `absolute` - if true, will return absolute paths instead of relative paths + /// * `canonicalize` - if true, will canonicalize entry paths before returned + /// * `include_root` - if true, will include the directory specified in the entries + async fn read_dir( + &self, + ctx: BoxedCtx, + path: PathBuf, + depth: usize, + absolute: bool, + canonicalize: bool, + include_root: bool, + ) -> io::Result<(Vec, Vec)>; + + /// Creates a directory. + /// + /// * `path` - the path to the directory + /// * `all` - if true, will create all missing parent components + async fn create_dir(&self, ctx: BoxedCtx, path: PathBuf, all: bool) -> io::Result<()>; + + /// Copies some file or directory. + /// + /// * `src` - the path to the file or directory to copy + /// * `dst` - the path where the copy will be placed + async fn copy(&self, ctx: BoxedCtx, src: PathBuf, dst: PathBuf) -> io::Result<()>; + + /// Removes some file or directory. + /// + /// * `path` - the path to a file or directory + /// * `force` - if true, will remove non-empty directories + async fn remove(&self, ctx: BoxedCtx, path: PathBuf, force: bool) -> io::Result<()>; + + /// Renames some file or directory. + /// + /// * `src` - the path to the file or directory to rename + /// * `dst` - the new name for the file or directory + async fn rename(&self, ctx: BoxedCtx, src: PathBuf, dst: PathBuf) -> io::Result<()>; + + /// Checks if the specified path exists. + /// + /// * `path` - the path to the file or directory + async fn exists(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result; + + /// Reads metadata for a file or directory. + /// + /// * `path` - the path to the file or directory + /// * `canonicalize` - if true, will include a canonicalized path in the metadata + /// * `resolve_file_type` - if true, will resolve symlinks to underlying type (file or dir) + async fn metadata( + &self, + ctx: BoxedCtx, + path: PathBuf, + canonicalize: bool, + resolve_file_type: bool, + ) -> io::Result; + + /// Sets permissions for a file, directory, or symlink. + /// + /// * `path` - the path to the file, directory, or symlink + /// * `resolve_symlink` - if true, will resolve the path to the underlying file/directory + /// * `permissions` - the new permissions to apply + async fn set_permissions( + &self, + ctx: BoxedCtx, + path: PathBuf, + permissions: Permissions, + options: SetPermissionsOptions, + ) -> io::Result<()>; +} /// API supporting process creation and manipulation. -pub trait ProcessApi {} +#[async_trait] +pub trait ProcessApi { + /// Spawns a new process, returning its id. + /// + /// * `cmd` - the full command to run as a new process (including arguments) + /// * `environment` - the environment variables to associate with the process + /// * `current_dir` - the alternative current directory to use with the process + /// * `pty` - if provided, will run the process within a PTY of the given size + async fn proc_spawn( + &self, + ctx: BoxedCtx, + cmd: String, + environment: Environment, + current_dir: Option, + pty: Option, + ) -> io::Result; + + /// Kills a running process by its id. + /// + /// * `id` - the unique id of the process + async fn proc_kill(&self, ctx: BoxedCtx, id: ProcessId) -> io::Result<()>; + + /// Sends data to the stdin of the process with the specified id. + /// + /// * `id` - the unique id of the process + /// * `data` - the bytes to send to stdin + async fn proc_stdin(&self, ctx: BoxedCtx, id: ProcessId, data: Vec) -> io::Result<()>; + + /// Resizes the PTY of the process with the specified id. + /// + /// * `id` - the unique id of the process + /// * `size` - the new size of the pty + async fn proc_resize_pty(&self, ctx: BoxedCtx, id: ProcessId, size: PtySize) -> io::Result<()>; +} /// API supporting searching through the remote system. -pub trait SearchApi {} +#[async_trait] +pub trait SearchApi { + /// Searches files for matches based on a query. + /// + /// * `query` - the specific query to perform + async fn search(&self, ctx: BoxedCtx, query: SearchQuery) -> io::Result; + + /// Cancels an actively-ongoing search. + /// + /// * `id` - the id of the search to cancel + async fn cancel_search(&self, ctx: BoxedCtx, id: SearchId) -> io::Result<()>; +} /// API supporting retrieval of information about the remote system. -pub trait SystemInfoApi {} +#[async_trait] +pub trait SystemInfoApi { + /// Retrieves information about the system. + async fn system_info(&self, ctx: BoxedCtx) -> io::Result; +} /// API supporting retrieval of the server's version. -pub trait VersionApi {} +#[async_trait] +pub trait VersionApi { + /// Retrieves information about the server's capabilities. + async fn version(&self, ctx: BoxedCtx) -> io::Result; +} + +/// API supporting watching of changes to the remote filesystem. +#[async_trait] +pub trait WatchApi { + /// Watches a file or directory for changes. + /// + /// * `path` - the path to the file or directory + /// * `recursive` - if true, will watch for changes within subdirectories and beyond + /// * `only` - if non-empty, will limit reported changes to those included in this list + /// * `except` - if non-empty, will limit reported changes to those not included in this list + async fn watch( + &self, + ctx: BoxedCtx, + path: PathBuf, + recursive: bool, + only: Vec, + except: Vec, + ) -> io::Result<()>; + + /// Removes a file or directory from being watched. + /// + /// * `path` - the path to the file or directory + async fn unwatch(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result<()>; +} + +pub use unsupported::Unsupported; + +mod unsupported { + use super::*; + + #[inline] + fn unsupported(label: &str) -> io::Result { + Err(io::Error::new( + io::ErrorKind::Unsupported, + format!("{label} is unsupported"), + )) + } + + /// Generic struct that implements all APIs as unsupported. + #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] + pub struct Unsupported; + + #[async_trait] + impl Api for Unsupported { + type FileSystem = Self; + type Process = Self; + type Search = Self; + type SystemInfo = Self; + type Version = Self; + type Watch = Self; + } + + #[async_trait] + impl FileSystemApi for Unsupported { + async fn read_file(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result> { + unsupported("read_file") + } + + async fn read_file_text(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result { + unsupported("read_file_text") + } + + async fn write_file(&self, ctx: BoxedCtx, path: PathBuf, data: Vec) -> io::Result<()> { + unsupported("write_file") + } + + async fn write_file_text( + &self, + ctx: BoxedCtx, + path: PathBuf, + data: String, + ) -> io::Result<()> { + unsupported("write_file_text") + } + + async fn append_file(&self, ctx: BoxedCtx, path: PathBuf, data: Vec) -> io::Result<()> { + unsupported("append_file") + } + + async fn append_file_text( + &self, + ctx: BoxedCtx, + path: PathBuf, + data: String, + ) -> io::Result<()> { + unsupported("append_file_text") + } + + async fn read_dir( + &self, + ctx: BoxedCtx, + path: PathBuf, + depth: usize, + absolute: bool, + canonicalize: bool, + include_root: bool, + ) -> io::Result<(Vec, Vec)> { + unsupported("read_dir") + } + + async fn create_dir(&self, ctx: BoxedCtx, path: PathBuf, all: bool) -> io::Result<()> { + unsupported("create_dir") + } + + async fn copy(&self, ctx: BoxedCtx, src: PathBuf, dst: PathBuf) -> io::Result<()> { + unsupported("copy") + } + + async fn remove(&self, ctx: BoxedCtx, path: PathBuf, force: bool) -> io::Result<()> { + unsupported("remove") + } + + async fn rename(&self, ctx: BoxedCtx, src: PathBuf, dst: PathBuf) -> io::Result<()> { + unsupported("rename") + } + + async fn exists(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result { + unsupported("exists") + } + + async fn metadata( + &self, + ctx: BoxedCtx, + path: PathBuf, + canonicalize: bool, + resolve_file_type: bool, + ) -> io::Result { + unsupported("metadata") + } + + async fn set_permissions( + &self, + ctx: BoxedCtx, + path: PathBuf, + permissions: Permissions, + options: SetPermissionsOptions, + ) -> io::Result<()> { + unsupported("set_permissions") + } + } + + #[async_trait] + impl ProcessApi for Unsupported { + async fn proc_spawn( + &self, + ctx: BoxedCtx, + cmd: String, + environment: Environment, + current_dir: Option, + pty: Option, + ) -> io::Result { + unsupported("proc_spawn") + } + + async fn proc_kill(&self, ctx: BoxedCtx, id: ProcessId) -> io::Result<()> { + unsupported("proc_kill") + } + + async fn proc_stdin(&self, ctx: BoxedCtx, id: ProcessId, data: Vec) -> io::Result<()> { + unsupported("proc_stdin") + } + + async fn proc_resize_pty( + &self, + ctx: BoxedCtx, + id: ProcessId, + size: PtySize, + ) -> io::Result<()> { + unsupported("proc_resize_pty") + } + } + + #[async_trait] + impl SearchApi for Unsupported { + async fn search(&self, ctx: BoxedCtx, query: SearchQuery) -> io::Result { + unsupported("search") + } + + async fn cancel_search(&self, ctx: BoxedCtx, id: SearchId) -> io::Result<()> { + unsupported("cancel_search") + } + } + + #[async_trait] + impl SystemInfoApi for Unsupported { + async fn system_info(&self, ctx: BoxedCtx) -> io::Result { + unsupported("system_info") + } + } + + #[async_trait] + impl VersionApi for Unsupported { + async fn version(&self, ctx: BoxedCtx) -> io::Result { + unsupported("version") + } + } -/// Generic struct that implements all APIs as unsupported. -pub struct Unsupported; + #[async_trait] + impl WatchApi for Unsupported { + async fn watch( + &self, + ctx: BoxedCtx, + path: PathBuf, + recursive: bool, + only: Vec, + except: Vec, + ) -> io::Result<()> { + unsupported("watch") + } -impl FileSystemApi for Unsupported { + async fn unwatch(&self, ctx: BoxedCtx, path: PathBuf) -> io::Result<()> { + unsupported("unwatch") + } + } } diff --git a/distant-plugin/src/api/ctx.rs b/distant-plugin/src/api/ctx.rs new file mode 100644 index 0000000..2618665 --- /dev/null +++ b/distant-plugin/src/api/ctx.rs @@ -0,0 +1,9 @@ +use async_trait::async_trait; + +/// Type abstraction of a boxed [`Ctx`]. +pub type BoxedCtx = Box; + +/// Represents a context associated when an API request is being executed, supporting the ability +/// to send responses back asynchronously. +#[async_trait] +pub trait Ctx: Send {}