fix: throw catchable errors instead of crashing when publishing a message containing invalid UTF-8 over DDS (#2893)

This commit is contained in:
三咲雅 misaki masa 2025-06-18 19:30:31 +08:00 committed by GitHub
parent a0ce7c0ae7
commit 750ca0c0d4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 145 additions and 97 deletions

View file

@ -7,6 +7,7 @@ use tokio::{fs::{self, OpenOptions}, io::AsyncWriteExt};
use yazi_config::YAZI;
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, max_common_root, maybe_exists, paths_to_same_file};
use yazi_macro::err;
use yazi_proxy::{AppProxy, HIDER, TasksProxy, WATCHER};
use yazi_shared::{terminal_clear, url::Url};
use yazi_term::tty::TTY;
@ -102,7 +103,8 @@ impl Mgr {
}
if !succeeded.is_empty() {
Pubsub::pub_from_bulk(succeeded.iter().map(|(o, n)| (o, &n.url)).collect());
let map = succeeded.iter().map(|(o, n)| (o, &n.url)).collect();
err!(Pubsub::pub_from_bulk(map));
FilesOp::rename(succeeded);
}
drop(permit);

View file

@ -5,6 +5,7 @@ use tokio::fs;
use yazi_config::popup::{ConfirmCfg, InputCfg};
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, maybe_exists, ok_or_not_found, paths_to_same_file, realname};
use yazi_macro::err;
use yazi_proxy::{ConfirmProxy, InputProxy, TabProxy, WATCHER};
use yazi_shared::{Id, event::CmdCow, url::{Url, UrnBuf}};
@ -92,7 +93,7 @@ impl Mgr {
}
TabProxy::reveal(&new);
Pubsub::pub_from_rename(tab, &old, &new);
err!(Pubsub::pub_from_rename(tab, &old, &new));
Ok(())
}

View file

@ -3,6 +3,7 @@ use std::ops::{Deref, DerefMut};
use yazi_boot::BOOT;
use yazi_dds::Pubsub;
use yazi_fs::File;
use yazi_macro::err;
use yazi_proxy::MgrProxy;
use yazi_shared::{Id, url::Url};
@ -38,7 +39,7 @@ impl Tabs {
self.cursor = idx;
MgrProxy::refresh();
MgrProxy::peek(true);
Pubsub::pub_from_tab(self.active().id);
err!(Pubsub::pub_from_tab(self.active().id));
}
}

View file

@ -43,10 +43,9 @@ impl Watcher {
}
#[cfg(any(target_os = "linux", target_os = "macos"))]
yazi_fs::mounts::Partitions::monitor(
yazi_fs::mounts::PARTITIONS.clone(),
yazi_dds::Pubsub::pub_from_mount,
);
yazi_fs::mounts::Partitions::monitor(yazi_fs::mounts::PARTITIONS.clone(), || {
yazi_macro::err!(yazi_dds::Pubsub::pub_from_mount())
});
tokio::spawn(Self::fan_out(out_rx));
Self { in_tx, out_tx }

View file

@ -2,6 +2,7 @@ use std::{collections::HashSet, ops::Deref};
use yazi_dds::Pubsub;
use yazi_fs::FilesOp;
use yazi_macro::err;
use yazi_shared::url::Url;
#[derive(Debug, Default)]
@ -65,7 +66,7 @@ impl Yanked {
}
self.version = self.revision;
Pubsub::pub_from_yank(self.cut, &self.urls);
err!(Pubsub::pub_from_yank(self.cut, &self.urls));
true
}
}

View file

@ -5,7 +5,7 @@ use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use yazi_config::popup::InputCfg;
use yazi_dds::Pubsub;
use yazi_fs::{File, FilesOp, expand_path};
use yazi_macro::render;
use yazi_macro::{err, render};
use yazi_proxy::{CmpProxy, InputProxy, MgrProxy, TabProxy};
use yazi_shared::{Debounce, errors::InputError, event::CmdCow, url::Url};
@ -73,7 +73,7 @@ impl Tab {
self.parent = Some(self.history.remove_or(&parent));
}
Pubsub::pub_from_cd(self.id, self.cwd());
err!(Pubsub::pub_from_cd(self.id, self.cwd()));
self.hover(None);
MgrProxy::refresh();

View file

@ -1,5 +1,5 @@
use yazi_dds::Pubsub;
use yazi_macro::render;
use yazi_macro::{err, render};
use yazi_shared::{event::CmdCow, url::{Url, Urn}};
use crate::tab::Tab;
@ -25,7 +25,7 @@ impl Tab {
}
// Publish through DDS
Pubsub::pub_from_hover(self.id, self.hovered().map(|h| &h.url));
err!(Pubsub::pub_from_hover(self.id, self.hovered().map(|h| &h.url)));
}
fn hover_do(&mut self, url: Url) {

View file

@ -3,6 +3,7 @@ use std::mem;
use yazi_config::{LAYOUT, YAZI};
use yazi_dds::Pubsub;
use yazi_fs::{File, Files, FilesOp, FolderStage, Step, cha::Cha};
use yazi_macro::err;
use yazi_proxy::MgrProxy;
use yazi_shared::{Id, url::{Url, Urn, UrnBuf}};
@ -88,7 +89,7 @@ impl Folder {
if !self.update(op) {
return false;
} else if self.stage != old {
Pubsub::pub_from_load(tab, &self.url, self.stage);
err!(Pubsub::pub_from_load(tab, &self.url, self.stage));
}
true
}

View file

@ -5,6 +5,7 @@ use parking_lot::RwLock;
use serde::{Deserialize, Serialize};
use tokio::{io::AsyncWriteExt, select, sync::mpsc, task::JoinHandle, time};
use tracing::error;
use yazi_macro::try_format;
use yazi_shared::{Id, RoCell};
use crate::{ClientReader, ClientWriter, Payload, Pubsub, Server, Stream, body::{Body, BodyBye, BodyHi}};
@ -55,7 +56,7 @@ impl Client {
continue;
} else if line.starts_with("hey,") {
Self::handle_hey(&line);
} else if let Err(e) = Payload::from_str(&line).map(|p| p.emit()) {
} else if let Err(e) = Payload::from_str(&line).and_then(|p| p.emit()) {
error!("Could not parse payload:\n{line}\n\nError:\n{e}");
}
}
@ -68,11 +69,11 @@ impl Client {
pub async fn shot(kind: &str, receiver: Id, body: &str) -> Result<()> {
Body::validate(kind)?;
let payload = format!(
let payload = try_format!(
"{}\n{kind},{receiver},{ID},{body}\n{}\n",
Payload::new(BodyHi::borrowed(Default::default())),
Payload::new(BodyBye::owned())
);
)?;
let (mut lines, mut writer) = Stream::connect().await?;
writer.write_all(payload.as_bytes()).await?;
@ -129,7 +130,7 @@ impl Client {
async fn make(kinds: &HashSet<&str>) -> Result<ClientReader> {
let (lines, mut writer) = Stream::connect().await?;
let hi = Payload::new(BodyHi::borrowed(kinds.clone()));
writer.write_all(format!("{hi}\n").as_bytes()).await?;
writer.write_all(try_format!("{hi}\n")?.as_bytes()).await?;
writer.flush().await?;
Ok(lines)
}
@ -155,8 +156,8 @@ impl Client {
}
#[inline]
pub(super) fn push<'a>(payload: impl Into<Payload<'a>>) {
QUEUE_TX.send(format!("{}\n", payload.into())).ok();
pub(super) fn push<'a>(payload: impl Into<Payload<'a>>) -> Result<()> {
Ok(QUEUE_TX.send(try_format!("{}\n", payload.into())?)?)
}
#[inline]

View file

@ -17,9 +17,12 @@ pub struct Payload<'a> {
impl<'a> Payload<'a> {
pub(super) fn new(body: Body<'a>) -> Self { Self { receiver: Id(0), sender: *ID, body } }
pub(super) fn flush(&self) { writeln!(std::io::stdout(), "{self}").ok(); }
pub(super) fn flush(&self) -> Result<()> {
writeln!(std::io::stdout(), "{self}")?;
Ok(())
}
pub(super) fn try_flush(&self) {
pub(super) fn try_flush(&self) -> Result<()> {
let b = if self.receiver == 0 {
BOOT.remote_events.contains(self.body.kind())
} else if let Body::Custom(b) = &self.body {
@ -27,10 +30,7 @@ impl<'a> Payload<'a> {
} else {
false
};
if b {
self.flush();
}
if b { self.flush() } else { Ok(()) }
}
pub(super) fn with_receiver(mut self, receiver: Id) -> Self {
@ -45,9 +45,10 @@ impl<'a> Payload<'a> {
}
impl Payload<'static> {
pub(super) fn emit(self) {
self.try_flush();
pub(super) fn emit(self) -> Result<()> {
self.try_flush()?;
emit!(Call(Cmd::new("app:accept_payload").with_any("payload", self)));
Ok(())
}
}

View file

@ -1,5 +1,6 @@
use std::collections::{HashMap, HashSet};
use anyhow::Result;
use mlua::Function;
use parking_lot::RwLock;
use yazi_boot::BOOT;
@ -62,159 +63,172 @@ impl Pubsub {
unsub!(REMOTE)(plugin, kind) && Self::pub_from_hi()
}
pub fn r#pub(body: Body<'static>) { body.with_receiver(*ID).emit(); }
pub fn r#pub(body: Body<'static>) -> Result<()> { body.with_receiver(*ID).emit() }
pub fn pub_to(receiver: Id, body: Body<'static>) {
pub fn pub_to(receiver: Id, body: Body<'static>) -> Result<()> {
if receiver == *ID {
return Self::r#pub(body);
}
let kind = body.kind();
if receiver == 0 && Self::any_remote_own(kind) {
Client::push(body);
Client::push(body)?;
} else if PEERS.read().get(&receiver).is_some_and(|c| c.able(kind)) {
Client::push(body.with_receiver(receiver));
Client::push(body.with_receiver(receiver))?;
}
Ok(())
}
pub fn pub_from_hi() -> bool {
let abilities = REMOTE.read().keys().cloned().collect();
let abilities = BOOT.remote_events.union(&abilities).map(|s| s.as_str()).collect();
Client::push(BodyHi::borrowed(abilities));
// FIXME: handle error
Client::push(BodyHi::borrowed(abilities)).ok();
true
}
pub fn pub_from_tab(idx: Id) {
pub fn pub_from_tab(idx: Id) -> Result<()> {
if LOCAL.read().contains_key("tab") {
Self::r#pub(BodyTab::owned(idx));
Self::r#pub(BodyTab::owned(idx))?;
}
if PEERS.read().values().any(|p| p.able("tab")) {
Client::push(BodyTab::owned(idx));
Client::push(BodyTab::owned(idx))?;
}
if BOOT.local_events.contains("tab") {
BodyTab::owned(idx).with_receiver(*ID).flush();
BodyTab::owned(idx).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_cd(tab: Id, url: &Url) {
pub fn pub_from_cd(tab: Id, url: &Url) -> Result<()> {
if LOCAL.read().contains_key("cd") {
Self::r#pub(BodyCd::dummy(tab));
Self::r#pub(BodyCd::dummy(tab))?;
}
if PEERS.read().values().any(|p| p.able("cd")) {
Client::push(BodyCd::borrowed(tab, url));
Client::push(BodyCd::borrowed(tab, url))?;
}
if BOOT.local_events.contains("cd") {
BodyCd::borrowed(tab, url).with_receiver(*ID).flush();
BodyCd::borrowed(tab, url).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_load(tab: Id, url: &Url, stage: FolderStage) {
pub fn pub_from_load(tab: Id, url: &Url, stage: FolderStage) -> Result<()> {
if LOCAL.read().contains_key("load") {
Self::r#pub(BodyLoad::dummy(tab, url, stage));
Self::r#pub(BodyLoad::dummy(tab, url, stage))?;
}
if PEERS.read().values().any(|p| p.able("load")) {
Client::push(BodyLoad::borrowed(tab, url, stage));
Client::push(BodyLoad::borrowed(tab, url, stage))?;
}
if BOOT.local_events.contains("load") {
BodyLoad::borrowed(tab, url, stage).with_receiver(*ID).flush();
BodyLoad::borrowed(tab, url, stage).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_hover(tab: Id, url: Option<&Url>) {
pub fn pub_from_hover(tab: Id, url: Option<&Url>) -> Result<()> {
if LOCAL.read().contains_key("hover") {
Self::r#pub(BodyHover::dummy(tab));
Self::r#pub(BodyHover::dummy(tab))?;
}
if PEERS.read().values().any(|p| p.able("hover")) {
Client::push(BodyHover::borrowed(tab, url));
Client::push(BodyHover::borrowed(tab, url))?;
}
if BOOT.local_events.contains("hover") {
BodyHover::borrowed(tab, url).with_receiver(*ID).flush();
BodyHover::borrowed(tab, url).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_rename(tab: Id, from: &Url, to: &Url) {
pub fn pub_from_rename(tab: Id, from: &Url, to: &Url) -> Result<()> {
if LOCAL.read().contains_key("rename") {
Self::r#pub(BodyRename::dummy(tab, from, to));
Self::r#pub(BodyRename::dummy(tab, from, to))?;
}
if PEERS.read().values().any(|p| p.able("rename")) {
Client::push(BodyRename::borrowed(tab, from, to));
Client::push(BodyRename::borrowed(tab, from, to))?;
}
if BOOT.local_events.contains("rename") {
BodyRename::borrowed(tab, from, to).with_receiver(*ID).flush();
BodyRename::borrowed(tab, from, to).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_bulk(changes: HashMap<&Url, &Url>) {
pub fn pub_from_bulk(changes: HashMap<&Url, &Url>) -> Result<()> {
if LOCAL.read().contains_key("bulk") {
Self::r#pub(BodyBulk::owned(&changes));
Self::r#pub(BodyBulk::owned(&changes))?;
}
if PEERS.read().values().any(|p| p.able("bulk")) {
Client::push(BodyBulk::borrowed(&changes));
Client::push(BodyBulk::borrowed(&changes))?;
}
if BOOT.local_events.contains("bulk") {
BodyBulk::borrowed(&changes).with_receiver(*ID).flush();
BodyBulk::borrowed(&changes).with_receiver(*ID).flush()?;
}
Ok(())
}
pub fn pub_from_yank(cut: bool, urls: &HashSet<Url>) {
pub fn pub_from_yank(cut: bool, urls: &HashSet<Url>) -> Result<()> {
if LOCAL.read().contains_key("@yank") {
Self::r#pub(BodyYank::dummy());
Self::r#pub(BodyYank::dummy())?;
}
if Self::any_remote_own("@yank") {
Client::push(BodyYank::borrowed(cut, urls));
Client::push(BodyYank::borrowed(cut, urls))?;
}
if BOOT.local_events.contains("@yank") {
BodyYank::borrowed(cut, urls).with_receiver(*ID).flush();
BodyYank::borrowed(cut, urls).with_receiver(*ID).flush()?;
}
Ok(())
}
pub(super) fn pub_from_move(items: Vec<BodyMoveItem>) {
pub(super) fn pub_from_move(items: Vec<BodyMoveItem>) -> Result<()> {
if PEERS.read().values().any(|p| p.able("move")) {
Client::push(BodyMove::borrowed(&items));
Client::push(BodyMove::borrowed(&items))?;
}
if BOOT.local_events.contains("move") {
BodyMove::borrowed(&items).with_receiver(*ID).flush();
BodyMove::borrowed(&items).with_receiver(*ID).flush()?;
}
if LOCAL.read().contains_key("move") {
Self::r#pub(BodyMove::owned(items));
Self::r#pub(BodyMove::owned(items))?;
}
Ok(())
}
pub(super) fn pub_from_trash(urls: Vec<Url>) {
pub(super) fn pub_from_trash(urls: Vec<Url>) -> Result<()> {
if PEERS.read().values().any(|p| p.able("trash")) {
Client::push(BodyTrash::borrowed(&urls));
Client::push(BodyTrash::borrowed(&urls))?;
}
if BOOT.local_events.contains("trash") {
BodyTrash::borrowed(&urls).with_receiver(*ID).flush();
BodyTrash::borrowed(&urls).with_receiver(*ID).flush()?;
}
if LOCAL.read().contains_key("trash") {
Self::r#pub(BodyTrash::owned(urls));
Self::r#pub(BodyTrash::owned(urls))?;
}
Ok(())
}
pub(super) fn pub_from_delete(urls: Vec<Url>) {
pub(super) fn pub_from_delete(urls: Vec<Url>) -> Result<()> {
if PEERS.read().values().any(|p| p.able("delete")) {
Client::push(BodyDelete::borrowed(&urls));
Client::push(BodyDelete::borrowed(&urls))?;
}
if BOOT.local_events.contains("delete") {
BodyDelete::borrowed(&urls).with_receiver(*ID).flush();
BodyDelete::borrowed(&urls).with_receiver(*ID).flush()?;
}
if LOCAL.read().contains_key("delete") {
Self::r#pub(BodyDelete::owned(urls));
Self::r#pub(BodyDelete::owned(urls))?;
}
Ok(())
}
pub fn pub_from_mount() {
pub fn pub_from_mount() -> Result<()> {
if LOCAL.read().contains_key("mount") {
Self::r#pub(BodyMount::owned());
Self::r#pub(BodyMount::owned())?;
}
if PEERS.read().values().any(|p| p.able("mount")) {
Client::push(BodyMount::owned());
Client::push(BodyMount::owned())?;
}
if BOOT.local_events.contains("mount") {
BodyMount::owned().with_receiver(*ID).flush();
BodyMount::owned().with_receiver(*ID).flush()?;
}
Ok(())
}
#[inline]

View file

@ -4,6 +4,7 @@ use parking_lot::Mutex;
use tokio::{pin, select, sync::mpsc};
use tokio_stream::{StreamExt, wrappers::UnboundedReceiverStream};
use tokio_util::sync::CancellationToken;
use yazi_macro::err;
use yazi_shared::{RoCell, url::Url};
use crate::{Pubsub, body::BodyMoveItem};
@ -61,9 +62,9 @@ impl Pump {
loop {
select! {
Some(items) = move_rx.next() => Pubsub::pub_from_move(items),
Some(urls) = trash_rx.next() => Pubsub::pub_from_trash(urls),
Some(urls) = delete_rx.next() => Pubsub::pub_from_delete(urls),
Some(items) = move_rx.next() => err!(Pubsub::pub_from_move(items)),
Some(urls) = trash_rx.next() => err!(Pubsub::pub_from_trash(urls)),
Some(urls) = delete_rx.next() => err!(Pubsub::pub_from_delete(urls)),
else => {
CT.cancel();
break;

View file

@ -3,6 +3,7 @@ use std::{collections::HashMap, str::FromStr, time::Duration};
use anyhow::Result;
use parking_lot::RwLock;
use tokio::{io::{AsyncBufReadExt, AsyncWriteExt, BufReader}, select, sync::mpsc::{self, UnboundedReceiver}, task::JoinHandle, time};
use yazi_macro::try_format;
use yazi_shared::{Id, RoCell};
use crate::{Client, ClientWriter, Payload, Peer, STATE, Stream, body::{Body, BodyBye, BodyHey}};
@ -109,13 +110,12 @@ impl Server {
}
fn handle_hey(clients: &HashMap<Id, Client>) {
let payload = format!(
"{}\n",
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()));
let payload = Payload::new(BodyHey::owned(
clients.values().map(|c| (c.id, Peer::new(&c.abilities))).collect(),
));
if let Ok(s) = try_format!("{payload}\n") {
clients.values().for_each(|c| _ = c.tx.send(s.clone()));
}
}
async fn handle_bye(id: Id, mut rx: UnboundedReceiver<String>, mut writer: ClientWriter) {
@ -125,10 +125,10 @@ impl Server {
}
}
_ = writer
.write_all(BodyBye::owned().with_receiver(id).with_sender(Id(0)).to_string().as_bytes())
.await;
writer.flush().await.ok();
let bye = BodyBye::owned().with_receiver(id).with_sender(Id(0));
if let Ok(s) = try_format!("{bye}") {
writer.write_all(s.as_bytes()).await.ok();
writer.flush().await.ok();
}
}
}

View file

@ -12,7 +12,10 @@ use yazi_shared::natsort;
use super::{Locked, Partition, Partitions};
impl Partitions {
pub fn monitor(me: Locked, cb: fn()) {
pub fn monitor<F>(me: Locked, cb: F)
where
F: Fn() + Copy + Send + 'static,
{
tokio::task::spawn_blocking(move || {
let session = unsafe { DASessionCreate(kCFAllocatorDefault) };
if session.is_null() {
@ -41,7 +44,7 @@ impl Partitions {
if mem::replace(&mut me.write().need_update, true) {
return;
}
Self::update(me.clone(), cb);
Self::update(me.clone(), move || _ = cb());
});
Box::into_raw(Box::new(boxed)) as *mut c_void
};

9
yazi-macro/src/fmt.rs Normal file
View file

@ -0,0 +1,9 @@
#[macro_export]
macro_rules! try_format {
($($arg:tt)*) => {{
use std::fmt::Write;
let mut output = String::new();
output.write_fmt(format_args!($($arg)*)).map(|_| output)
}}
}

View file

@ -1,5 +1,6 @@
mod asset;
mod event;
mod fmt;
mod log;
mod module;
mod platform;

View file

@ -17,3 +17,18 @@ macro_rules! time {
}
}};
}
#[macro_export]
macro_rules! err {
($expr:expr) => {
$crate::err!(stringify!($expr), $expr)
};
($label:expr, $expr:expr) => {
$crate::err!($expr, "{}", $label)
};
($expr:expr, $fmt:expr, $($args:tt)*) => {{
if let Err(e) = $expr {
tracing::error!("{} failed: {e}", format_args!($fmt, $($args)*));
}
}};
}

View file

@ -9,15 +9,13 @@ pub struct Pubsub;
impl Pubsub {
pub(super) fn r#pub(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (kind, value): (mlua::String, Value)| {
yazi_dds::Pubsub::r#pub(Body::from_lua(&kind.to_str()?, value)?);
Ok(())
yazi_dds::Pubsub::r#pub(Body::from_lua(&kind.to_str()?, value)?).into_lua_err()
})
}
pub(super) fn pub_to(lua: &Lua) -> mlua::Result<Function> {
lua.create_function(|_, (receiver, kind, value): (Id, mlua::String, Value)| {
yazi_dds::Pubsub::pub_to(*receiver, Body::from_lua(&kind.to_str()?, value)?);
Ok(())
yazi_dds::Pubsub::pub_to(*receiver, Body::from_lua(&kind.to_str()?, value)?).into_lua_err()
})
}