diff --git a/CHANGELOG.md b/CHANGELOG.md index b5d3757..810a437 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,10 +7,23 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Changed + +- `distant_protocol::PROTOCOL_VERSION` now uses the crate's major, minor, and + patch version at compile-time (parsed via `const-str` crate) to streamline + version handling between crate and protocol + ### Fixed - CLI commands like `distant manager select` will now output errors in a JSON format when configured to communicate using JSON +- `distant-ssh2` no longer caches the remote family globally, but instead + caches it per `Ssh` instance + +### Removed + +- `Cmd::program` and `Cmd::arguments` functions as they were misleading (didn't + do what `distant-local` or `distant-ssh2` do) ## [0.20.0-alpha.12] diff --git a/Cargo.lock b/Cargo.lock index 5a96fd5..966cdc5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -571,6 +571,12 @@ version = "0.9.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "520fbf3c07483f94e3e3ca9d0cfd913d7718ef2483d2cfd91c0d9e91474ab913" +[[package]] +name = "const-str" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aca749d3d3f5b87a0d6100509879f9cf486ab510803a4a4e1001da1ff61c2bd6" + [[package]] name = "convert_case" version = "0.4.0" @@ -952,6 +958,7 @@ name = "distant-protocol" version = "0.20.0-alpha.12" dependencies = [ "bitflags 2.3.1", + "const-str", "derive_more", "regex", "rmp", diff --git a/distant-protocol/Cargo.toml b/distant-protocol/Cargo.toml index 604fc6f..e10d357 100644 --- a/distant-protocol/Cargo.toml +++ b/distant-protocol/Cargo.toml @@ -17,6 +17,7 @@ tests = [] [dependencies] bitflags = "2.3.1" +const-str = "0.5.6" derive_more = { version = "0.99.17", default-features = false, features = ["deref", "deref_mut", "display", "from", "error", "into", "into_iterator", "is_variant"] } regex = "1.8.3" serde = { version = "1.0.163", features = ["derive"] } diff --git a/distant-protocol/src/common/cmd.rs b/distant-protocol/src/common/cmd.rs index 786531b..073f7d0 100644 --- a/distant-protocol/src/common/cmd.rs +++ b/distant-protocol/src/common/cmd.rs @@ -12,22 +12,6 @@ impl Cmd { pub fn new(cmd: impl Into) -> Self { Self(cmd.into()) } - - /// Returns reference to the program portion of the command - pub fn program(&self) -> &str { - match self.0.split_once(' ') { - Some((program, _)) => program.trim(), - None => self.0.trim(), - } - } - - /// Returns reference to the arguments portion of the command - pub fn arguments(&self) -> &str { - match self.0.split_once(' ') { - Some((_, arguments)) => arguments.trim(), - None => "", - } - } } impl Deref for Cmd { diff --git a/distant-protocol/src/lib.rs b/distant-protocol/src/lib.rs index 1da41ad..8f0907c 100644 --- a/distant-protocol/src/lib.rs +++ b/distant-protocol/src/lib.rs @@ -17,7 +17,11 @@ pub use response::*; /// Protocol version indicated by the tuple of (major, minor, patch). /// -/// This is different from the crate version, which matches that of the complete suite of distant -/// crates. Rather, this verison is used to provide stability indicators when the protocol itself -/// changes across crate versions. -pub const PROTOCOL_VERSION: SemVer = (0, 1, 0); +/// This should match the version of this crate such that any significant change to the crate +/// version will also be reflected in this constant that can be used to verify compatibility across +/// the wire. +pub const PROTOCOL_VERSION: SemVer = ( + const_str::parse!(env!("CARGO_PKG_VERSION_MAJOR"), u8), + const_str::parse!(env!("CARGO_PKG_VERSION_MINOR"), u8), + const_str::parse!(env!("CARGO_PKG_VERSION_PATCH"), u8), +); diff --git a/distant-ssh2/src/lib.rs b/distant-ssh2/src/lib.rs index f8cbd7d..aefe3be 100644 --- a/distant-ssh2/src/lib.rs +++ b/distant-ssh2/src/lib.rs @@ -16,7 +16,6 @@ use std::str::FromStr; use std::time::Duration; use async_compat::CompatExt; -use async_once_cell::OnceCell; use async_trait::async_trait; use distant_core::net::auth::{AuthHandlerMap, DummyAuthHandler, Verifier}; use distant_core::net::client::{Client, ClientConfig}; @@ -25,6 +24,7 @@ use distant_core::net::server::{Server, ServerRef}; use distant_core::{DistantApiServerHandler, DistantClient, DistantSingleKeyCredentials}; use log::*; use smol::channel::Receiver as SmolReceiver; +use tokio::sync::Mutex; use wezterm_ssh::{ ChildKiller, Config as WezConfig, MasterPty, PtySize, Session as WezSession, SessionEvent as WezSessionEvent, @@ -325,17 +325,20 @@ impl SshAuthHandler for LocalSshAuthHandler { } } -/// Represents an ssh2 client +/// Represents an ssh2 client. pub struct Ssh { session: WezSession, events: SmolReceiver, host: String, port: u16, authenticated: bool, + + /// Cached copy of the family representing the remote machine. + cached_family: Mutex>, } impl Ssh { - /// Connect to a remote TCP server using SSH + /// Connect to a remote TCP server using SSH. pub fn connect(host: impl AsRef, opts: SshOpts) -> io::Result { debug!( "Establishing ssh connection to {} using {:?}", @@ -416,15 +419,16 @@ impl Ssh { host: host.as_ref().to_string(), port, authenticated: false, + cached_family: Mutex::new(None), }) } - /// Host this client is connected to + /// Host this client is connected to. pub fn host(&self) -> &str { &self.host } - /// Port this client is connected to on remote host + /// Port this client is connected to on remote host. pub fn port(&self) -> u16 { self.port } @@ -434,7 +438,7 @@ impl Ssh { self.authenticated } - /// Authenticates the [`Ssh`] if not already authenticated + /// Authenticates the [`Ssh`] if not already authenticated. pub async fn authenticate(&mut self, handler: impl SshAuthHandler) -> io::Result<()> { // If already authenticated, exit if self.authenticated { @@ -499,10 +503,10 @@ impl Ssh { Ok(()) } - /// Detects the family of operating system on the remote machine + /// Detects the family of operating system on the remote machine. + /// + /// Caches the result such that subsequent checks will return the same family. pub async fn detect_family(&self) -> io::Result { - static INSTANCE: OnceCell = OnceCell::new(); - // Exit early if not authenticated as this is a requirement if !self.authenticated { return Err(io::Error::new( @@ -511,18 +515,23 @@ impl Ssh { )); } - INSTANCE - .get_or_try_init(async move { - let is_windows = utils::is_windows(&self.session).await?; + let mut family = self.cached_family.lock().await; - Ok(if is_windows { - SshFamily::Windows - } else { - SshFamily::Unix - }) - }) - .await - .copied() + // Family value is not present, so we retrieve it now and populate our cache + if family.is_none() { + // Check if we are windows, otherwise assume unix, returning an error if encountered, + // which will also drop our lock on the cache + let is_windows = utils::is_windows(&self.session).await?; + + *family = Some(if is_windows { + SshFamily::Windows + } else { + SshFamily::Unix + }); + } + + // Cache should always be Some(...) by this point + Ok(family.unwrap()) } /// Consume [`Ssh`] and produce a [`DistantClient`] that is connected to a remote