feat!: DDS client-server version check (#1111)

This commit is contained in:
三咲雅 · Misaki Masa 2024-06-02 03:04:53 +08:00 committed by GitHub
parent add801f28e
commit e4d67121f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 136 additions and 72 deletions

17
Cargo.lock generated
View File

@ -1698,9 +1698,9 @@ checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
[[package]]
name = "serde"
version = "1.0.202"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "226b61a0d411b2ba5ff6d7f73a476ac4f8bb900373459cd00fab8512828ba395"
checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094"
dependencies = [
"serde_derive",
]
@ -1717,9 +1717,9 @@ dependencies = [
[[package]]
name = "serde_derive"
version = "1.0.202"
version = "1.0.203"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6048858004bcff69094cd972ed40a32500f153bd3be9f716b2eed2e8217c4838"
checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba"
dependencies = [
"proc-macro2",
"quote",
@ -2052,9 +2052,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]]
name = "tokio"
version = "1.37.0"
version = "1.38.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787"
checksum = "ba4f4a02a7a80d6f274636f0aa95c7e383b912d41fe721a31f29e29698585a4a"
dependencies = [
"backtrace",
"bytes",
@ -2071,9 +2071,9 @@ dependencies = [
[[package]]
name = "tokio-macros"
version = "2.2.0"
version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b"
checksum = "5f5ae998a069d4b5aba8ee9dad856af7d520c3699e6159b185c2acd48155d39a"
dependencies = [
"proc-macro2",
"quote",
@ -2807,6 +2807,7 @@ dependencies = [
"tokio-stream",
"tokio-util",
"uzers",
"vergen",
"yazi-boot",
"yazi-shared",
]

View File

@ -25,7 +25,7 @@ imagesize = "0.12.0"
kamadak-exif = "0.5.5"
ratatui = "0.26.3"
scopeguard = "1.2.0"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View File

@ -15,7 +15,7 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
clap = { version = "4.5.4", features = [ "derive" ] }
serde = { version = "1.0.202", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
[build-dependencies]
clap = { version = "4.5.4", features = [ "derive" ] }

View File

@ -18,7 +18,7 @@ clap = { version = "4.5.4", features = [ "derive" ] }
crossterm = "0.27.0"
md-5 = "0.10.6"
serde_json = "1.0.117"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
toml_edit = "0.22.13"
[build-dependencies]

View File

@ -26,12 +26,12 @@ pub(super) enum Command {
#[derive(clap::Args)]
pub(super) struct CommandPub {
/// The receiver ID.
#[arg(index = 1)]
pub(super) receiver: u64,
/// The kind of message.
#[arg(index = 2)]
#[arg(index = 1)]
pub(super) kind: String,
/// The receiver ID.
#[arg(index = 2)]
pub(super) receiver: Option<u64>,
/// Send the message with a string body.
#[arg(long)]
pub(super) str: Option<String>,
@ -41,6 +41,17 @@ pub(super) struct CommandPub {
}
impl CommandPub {
#[allow(dead_code)]
pub(super) fn receiver(&self) -> Result<u64> {
if let Some(receiver) = self.receiver {
Ok(receiver)
} else if let Ok(s) = std::env::var("YAZI_ID") {
Ok(s.parse()?)
} else {
bail!("No receiver ID provided, also no YAZI_ID environment variable found.")
}
}
#[allow(dead_code)]
pub(super) fn body(&self) -> Result<Cow<str>> {
if let Some(json) = &self.json {
@ -48,19 +59,19 @@ impl CommandPub {
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
} else {
bail!("No body provided");
Ok("".into())
}
}
}
#[derive(clap::Args)]
pub(super) struct CommandPubStatic {
/// The severity of the message.
#[arg(index = 1)]
pub(super) severity: u16,
/// The kind of message.
#[arg(index = 2)]
#[arg(index = 1)]
pub(super) kind: String,
/// The severity of the message.
#[arg(index = 2)]
pub(super) severity: u16,
/// Send the message with a string body.
#[arg(long)]
pub(super) str: Option<String>,
@ -77,7 +88,7 @@ impl CommandPubStatic {
} else if let Some(str) = &self.str {
Ok(serde_json::to_string(str)?.into())
} else {
bail!("No body provided");
Ok("".into())
}
}
}

View File

@ -19,7 +19,7 @@ async fn main() -> anyhow::Result<()> {
match Args::parse().command {
Command::Pub(cmd) => {
yazi_dds::init();
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver, None, &cmd.body()?).await {
if let Err(e) = yazi_dds::Client::shot(&cmd.kind, cmd.receiver()?, None, &cmd.body()?).await {
eprintln!("Cannot send message: {e}");
std::process::exit(1);
}

View File

@ -18,7 +18,7 @@ crossterm = "0.27.0"
globset = "0.4.14"
indexmap = "2.2.6"
ratatui = "0.26.3"
serde = { version = "1.0.202", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0"
toml = { version = "0.8.13", features = [ "preserve_order" ] }
validator = { version = "0.18.1", features = [ "derive" ] }

View File

@ -29,9 +29,9 @@ parking_lot = "0.12.3"
ratatui = "0.26.3"
regex = "1.10.4"
scopeguard = "1.2.0"
serde = "1.0.202"
serde = "1.0.203"
shell-words = "1.1.0"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
unicode-width = "0.1.12"

View File

@ -8,8 +8,8 @@ bitflags! {
pub struct Opt: u8 {
const FIND = 0b00001;
const VISUAL = 0b00010;
const SELECT = 0b00100;
const FILTER = 0b01000;
const FILTER = 0b00100;
const SELECT = 0b01000;
const SEARCH = 0b10000;
}
}
@ -21,8 +21,8 @@ impl From<Cmd> for Opt {
("all", true) => Self::all(),
("find", true) => acc | Self::FIND,
("visual", true) => acc | Self::VISUAL,
("select", true) => acc | Self::SELECT,
("filter", true) => acc | Self::FILTER,
("select", true) => acc | Self::SELECT,
("search", true) => acc | Self::SEARCH,
_ => acc,
}
@ -36,8 +36,8 @@ impl Tab {
if opt.is_empty() {
_ = self.escape_find()
|| self.escape_visual()
|| self.escape_select()
|| self.escape_filter()
|| self.escape_select()
|| self.escape_search();
return;
}
@ -48,12 +48,12 @@ impl Tab {
if opt.contains(Opt::VISUAL) {
self.escape_visual();
}
if opt.contains(Opt::SELECT) {
self.escape_select();
}
if opt.contains(Opt::FILTER) {
self.escape_filter();
}
if opt.contains(Opt::SELECT) {
self.escape_select();
}
if opt.contains(Opt::SEARCH) {
self.escape_search();
}
@ -70,6 +70,15 @@ impl Tab {
true
}
pub fn escape_filter(&mut self) -> bool {
if self.current.files.filter().is_none() {
return false;
}
self.filter_do(super::filter::Opt::default());
render_and!(true)
}
pub fn escape_select(&mut self) -> bool {
if self.selected.is_empty() {
return false;
@ -82,15 +91,6 @@ impl Tab {
render_and!(true)
}
pub fn escape_filter(&mut self) -> bool {
if self.current.files.filter().is_none() {
return false;
}
self.filter_do(super::filter::Opt::default());
render_and!(true)
}
pub fn escape_search(&mut self) -> bool {
if !self.current.cwd.is_search() {
return false;

View File

@ -20,11 +20,14 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
anyhow = "1.0.86"
mlua = { version = "0.9.8", features = [ "lua54" ] }
parking_lot = "0.12.3"
serde = { version = "1.0.202", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
serde_json = "1.0.117"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
[build-dependencies]
vergen = { version = "8.3.1", features = [ "build", "git", "gitcl" ] }
[target."cfg(unix)".dependencies]
uzers = "0.12.0"

9
yazi-dds/build.rs Normal file
View File

@ -0,0 +1,9 @@
use std::error::Error;
use vergen::EmitBuilder;
fn main() -> Result<(), Box<dyn Error>> {
EmitBuilder::builder().git_sha(true).emit()?;
Ok(())
}

View File

@ -4,11 +4,11 @@ use serde::{Deserialize, Serialize};
use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyBye {}
pub struct BodyBye;
impl BodyBye {
#[inline]
pub fn borrowed() -> Body<'static> { Self {}.into() }
pub fn owned() -> Body<'static> { Self.into() }
}
impl<'a> From<BodyBye> for Body<'a> {

View File

@ -3,12 +3,20 @@ use std::collections::HashMap;
use mlua::{ExternalResult, IntoLua, Lua, Value};
use serde::{Deserialize, Serialize};
use super::Body;
use super::{Body, BodyHi};
use crate::Peer;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyHey {
pub peers: HashMap<u64, Peer>,
pub peers: HashMap<u64, Peer>,
pub version: String,
}
impl BodyHey {
#[inline]
pub fn owned(peers: HashMap<u64, Peer>) -> Body<'static> {
Self { peers, version: BodyHi::version() }.into()
}
}
impl From<BodyHey> for Body<'_> {

View File

@ -8,13 +8,21 @@ use super::Body;
#[derive(Debug, Serialize, Deserialize)]
pub struct BodyHi<'a> {
pub abilities: HashSet<Cow<'a, String>>,
pub version: String,
}
impl<'a> BodyHi<'a> {
#[inline]
pub fn borrowed(abilities: HashSet<&'a String>) -> Body<'a> {
Self { abilities: abilities.into_iter().map(Cow::Borrowed).collect() }.into()
Self {
abilities: abilities.into_iter().map(Cow::Borrowed).collect(),
version: Self::version(),
}
.into()
}
#[inline]
pub fn version() -> String { format!("{} {}", env!("CARGO_PKG_VERSION"), env!("VERGEN_GIT_SHA")) }
}
impl<'a> From<BodyHi<'a>> for Body<'a> {

View File

@ -1,6 +1,6 @@
use std::{collections::{HashMap, HashSet}, mem, str::FromStr};
use anyhow::Result;
use anyhow::{bail, Result};
use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
@ -69,7 +69,7 @@ impl Client {
let payload = format!(
"{}\n{kind},{receiver},{sender},{body}\n{}\n",
Payload::new(BodyHi::borrowed(Default::default())),
Payload::new(BodyBye::borrowed())
Payload::new(BodyBye::owned())
);
let (mut lines, mut writer) = Stream::connect().await?;
@ -77,12 +77,26 @@ impl Client {
writer.flush().await?;
drop(writer);
while let Ok(Some(s)) = lines.next_line().await {
if matches!(s.split(',').next(), Some(kind) if kind == "bye") {
break;
let mut version = None;
while let Ok(Some(line)) = lines.next_line().await {
match line.split(',').next() {
Some("hey") if version.is_none() => {
if let Ok(Body::Hey(hey)) = Payload::from_str(&line).map(|p| p.body) {
version = Some(hey.version);
}
}
Some("bye") => break,
_ => {}
}
}
if version != Some(BodyHi::version()) {
bail!(
"Incompatible version (Ya {}, Yazi {})",
BodyHi::version(),
version.as_deref().unwrap_or("Unknown")
);
}
Ok(())
}

View File

@ -2,10 +2,10 @@ use std::{collections::HashMap, str::FromStr, time::Duration};
use anyhow::Result;
use parking_lot::RwLock;
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc, task::JoinHandle, time};
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time};
use yazi_shared::RoCell;
use crate::{body::{Body, BodyBye, BodyHey}, Client, Payload, Peer, Stream, STATE};
use crate::{body::{Body, BodyBye, BodyHey}, Client, ClientWriter, Payload, Peer, Stream, STATE};
pub(super) static CLIENTS: RoCell<RwLock<HashMap<u64, Client>>> = RoCell::new();
@ -44,7 +44,7 @@ impl Server {
let Some(id) = id else { continue };
if line.starts_with("bye,") {
writer.write_all(BodyBye::borrowed().with_receiver(id).with_sender(0).to_string().as_bytes()).await.ok();
Self::handle_bye(id, rx, writer).await;
break;
}
@ -77,7 +77,11 @@ impl Server {
else => break
}
}
Self::handle_bye(id);
let mut clients = CLIENTS.write();
if id.and_then(|id| clients.remove(&id)).is_some() {
Self::handle_hey(&clients);
}
});
}
}))
@ -111,18 +115,24 @@ impl Server {
fn handle_hey(clients: &HashMap<u64, Client>) {
let payload = format!(
"{}\n",
Payload::new(
BodyHey { peers: clients.values().map(|c| (c.id, Peer::new(&c.abilities))).collect() }
.into()
)
Payload::new(BodyHey::owned(
clients.values().map(|c| (c.id, Peer::new(&c.abilities))).collect()
))
);
clients.values().for_each(|c| _ = c.tx.send(payload.clone()));
}
fn handle_bye(id: Option<u64>) {
let mut clients = CLIENTS.write();
if id.and_then(|id| clients.remove(&id)).is_some() {
Self::handle_hey(&clients);
async fn handle_bye(id: u64, mut rx: UnboundedReceiver<String>, mut writer: ClientWriter) {
while let Ok(payload) = rx.try_recv() {
if writer.write_all(payload.as_bytes()).await.is_err() {
break;
}
}
_ = writer
.write_all(BodyBye::owned().with_receiver(id).with_sender(0).to_string().as_bytes())
.await;
writer.flush().await.ok();
}
}

View File

@ -32,7 +32,7 @@ mlua = { version = "0.9.8", features = [ "lua54" ] }
ratatui = "0.26.3"
scopeguard = "1.2.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-util = "0.7.11"
# Logging

View File

@ -30,12 +30,12 @@ md-5 = "0.10.6"
mlua = { version = "0.9.8", features = [ "lua54", "serialize", "macros", "async" ] }
parking_lot = "0.12.3"
ratatui = "0.26.3"
serde = "1.0.202"
serde = "1.0.203"
serde_json = "1.0.117"
shell-escape = "0.1.5"
shell-words = "1.1.0"
syntect = { version = "5.2.0", default-features = false, features = [ "parsing", "plist-load", "regex-onig" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
tokio-stream = "0.1.15"
tokio-util = "0.7.11"
unicode-width = "0.1.12"

View File

@ -19,4 +19,4 @@ yazi-shared = { path = "../yazi-shared", version = "0.2.5" }
# External dependencies
anyhow = "1.0.86"
mlua = { version = "0.9.8", features = [ "lua54" ] }
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }

View File

@ -21,7 +21,7 @@ async-priority-channel = "0.2.0"
futures = "0.3.30"
parking_lot = "0.12.3"
scopeguard = "1.2.0"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }

View File

@ -20,9 +20,9 @@ parking_lot = "0.12.3"
percent-encoding = "2.3.1"
ratatui = "0.26.3"
regex = "1.10.4"
serde = { version = "1.0.202", features = [ "derive" ] }
serde = { version = "1.0.203", features = [ "derive" ] }
shell-words = "1.1.0"
tokio = { version = "1.37.0", features = [ "full" ] }
tokio = { version = "1.38.0", features = [ "full" ] }
# Logging
tracing = { version = "0.1.40", features = [ "max_level_debug", "release_max_level_warn" ] }