update notify-rust

This commit is contained in:
Robin Appelman 2026-04-15 01:06:22 +02:00
commit 1937fbd083
3 changed files with 69 additions and 206 deletions

172
Cargo.lock generated
View file

@ -147,79 +147,6 @@ dependencies = [
"pin-project-lite", "pin-project-lite",
] ]
[[package]]
name = "async-channel"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "924ed96dd52d1b75e9c1a3e6275715fd320f5f9439fb5a4a11fa51f4221158d2"
dependencies = [
"concurrent-queue",
"event-listener-strategy",
"futures-core",
"pin-project-lite",
]
[[package]]
name = "async-executor"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c96bf972d85afc50bf5ab8fe2d54d1586b4e0b46c97c50a0c9e71e2f7bcd812a"
dependencies = [
"async-task",
"concurrent-queue",
"fastrand",
"futures-lite",
"pin-project-lite",
"slab",
]
[[package]]
name = "async-io"
version = "2.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456b8a8feb6f42d237746d4b3e9a178494627745c3c56c6ea55d92ba50d026fc"
dependencies = [
"autocfg",
"cfg-if",
"concurrent-queue",
"futures-io",
"futures-lite",
"parking",
"polling",
"rustix",
"slab",
"windows-sys 0.61.2",
]
[[package]]
name = "async-lock"
version = "3.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f7f2596bd5b78a9fec8088ccd89180d7f9f55b94b0576823bbbdc72ee8311"
dependencies = [
"event-listener",
"event-listener-strategy",
"pin-project-lite",
]
[[package]]
name = "async-process"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc50921ec0055cdd8a16de48773bfeec5c972598674347252c0399676be7da75"
dependencies = [
"async-channel",
"async-io",
"async-lock",
"async-signal",
"async-task",
"blocking",
"cfg-if",
"event-listener",
"futures-lite",
"rustix",
]
[[package]] [[package]]
name = "async-recursion" name = "async-recursion"
version = "1.1.1" version = "1.1.1"
@ -231,30 +158,6 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "async-signal"
version = "0.2.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43c070bbf59cd3570b6b2dd54cd772527c7c3620fce8be898406dd3ed6adc64c"
dependencies = [
"async-io",
"async-lock",
"atomic-waker",
"cfg-if",
"futures-core",
"futures-io",
"rustix",
"signal-hook-registry",
"slab",
"windows-sys 0.61.2",
]
[[package]]
name = "async-task"
version = "4.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.89" version = "0.1.89"
@ -266,12 +169,6 @@ dependencies = [
"syn 2.0.117", "syn 2.0.117",
] ]
[[package]]
name = "atomic-waker"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0"
[[package]] [[package]]
name = "autocfg" name = "autocfg"
version = "1.5.0" version = "1.5.0"
@ -343,19 +240,6 @@ dependencies = [
"objc2", "objc2",
] ]
[[package]]
name = "blocking"
version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83f8d02be6967315521be875afa792a316e28d57b5a2d401897e2a7921b7f21"
dependencies = [
"async-channel",
"async-task",
"futures-io",
"futures-lite",
"piper",
]
[[package]] [[package]]
name = "borsh" name = "borsh"
version = "1.6.0" version = "1.6.0"
@ -838,6 +722,7 @@ dependencies = [
"ashpd", "ashpd",
"clap", "clap",
"ctrlc", "ctrlc",
"futures-util",
"hex", "hex",
"home", "home",
"main_error", "main_error",
@ -955,12 +840,6 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
[[package]]
name = "hermit-abi"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c"
[[package]] [[package]]
name = "hex" name = "hex"
version = "0.4.3" version = "0.4.3"
@ -1700,9 +1579,9 @@ dependencies = [
[[package]] [[package]]
name = "mac-notification-sys" name = "mac-notification-sys"
version = "0.6.9" version = "0.6.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fd3f75411f4725061682ed91f131946e912859d0044d39c4ec0aac818d7621" checksum = "29a16783dd1a47849b8c8133c9cd3eb2112cfbc6901670af3dba47c8bbfb07d3"
dependencies = [ dependencies = [
"cc", "cc",
"objc2", "objc2",
@ -1815,9 +1694,9 @@ dependencies = [
[[package]] [[package]]
name = "notify-rust" name = "notify-rust"
version = "4.13.0" version = "4.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9612133a804b4bc753f9f806de73fc9730b7694eb1bada2dc252cce022638be8" checksum = "3c8146c105ae33d744e2d645f684d063b01176a99daf5986556266777b428816"
dependencies = [ dependencies = [
"futures-lite", "futures-lite",
"log", "log",
@ -2032,31 +1911,6 @@ version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd"
[[package]]
name = "piper"
version = "0.2.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c835479a4443ded371d6c535cbfd8d31ad92c5d23ae9770a61bc155e4992a3c1"
dependencies = [
"atomic-waker",
"fastrand",
"futures-io",
]
[[package]]
name = "polling"
version = "3.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218"
dependencies = [
"cfg-if",
"concurrent-queue",
"hermit-abi",
"pin-project-lite",
"rustix",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "powerfmt" name = "powerfmt"
version = "0.2.0" version = "0.2.0"
@ -2699,22 +2553,22 @@ dependencies = [
[[package]] [[package]]
name = "time" name = "time"
version = "0.3.44" version = "0.3.45"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" checksum = "f9e442fc33d7fdb45aa9bfeb312c095964abdf596f7567261062b2a7107aaabd"
dependencies = [ dependencies = [
"deranged", "deranged",
"num-conv", "num-conv",
"powerfmt", "powerfmt",
"serde", "serde_core",
"time-core", "time-core",
] ]
[[package]] [[package]]
name = "time-core" name = "time-core"
version = "0.1.6" version = "0.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" checksum = "8b36ee98fd31ec7426d599183e8fe26932a8dc1fb76ddb6214d05493377d34ca"
[[package]] [[package]]
name = "tinystr" name = "tinystr"
@ -3785,14 +3639,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1" checksum = "1bfeff997a0aaa3eb20c4652baf788d2dfa6d2839a0ead0b3ff69ce2f9c4bdd1"
dependencies = [ dependencies = [
"async-broadcast", "async-broadcast",
"async-executor",
"async-io",
"async-lock",
"async-process",
"async-recursion", "async-recursion",
"async-task",
"async-trait", "async-trait",
"blocking",
"enumflags2", "enumflags2",
"event-listener", "event-listener",
"futures-core", "futures-core",

View file

@ -22,9 +22,10 @@ sha2 = "0.11.0"
hex = "0.4.3" hex = "0.4.3"
xee-xpath = "0.1.5" xee-xpath = "0.1.5"
xee-interpreter = "0.2.0" xee-interpreter = "0.2.0"
notify-rust = "4.13.0" notify-rust = { version = "4.13.0", default-features = false, features = ["z-with-tokio"] }
tokio = "1.51.0" tokio = "1.51.0"
ashpd = { version = "0.13.9", features = ["open_uri"] } ashpd = { version = "0.13.9", features = ["open_uri"] }
futures-util = "0.3.32"
[dev-dependencies] [dev-dependencies]
maplit = "1.0.2" maplit = "1.0.2"

View file

@ -9,7 +9,7 @@ use main_error::MainResult;
use notify_debouncer_full::notify::event::{AccessKind, AccessMode, ModifyKind, RenameMode}; use notify_debouncer_full::notify::event::{AccessKind, AccessMode, ModifyKind, RenameMode};
use notify_debouncer_full::notify::{EventKind, RecursiveMode}; use notify_debouncer_full::notify::{EventKind, RecursiveMode};
use notify_debouncer_full::{DebounceEventResult, new_debouncer}; use notify_debouncer_full::{DebounceEventResult, new_debouncer};
use notify_rust::{Hint, Notification}; use notify_rust::{ActionResponse, Hint, Notification};
use std::fs::{File, copy, create_dir_all, read_dir, remove_file, rename}; use std::fs::{File, copy, create_dir_all, read_dir, remove_file, rename};
use std::io::ErrorKind; use std::io::ErrorKind;
use std::os::unix::fs::symlink; use std::os::unix::fs::symlink;
@ -17,6 +17,7 @@ use std::path::{Path, PathBuf};
use std::sync::mpsc::channel; use std::sync::mpsc::channel;
use std::thread::{sleep, spawn}; use std::thread::{sleep, spawn};
use std::time::Duration; use std::time::Duration;
use tokio::time::timeout;
use tracing::{debug, error, info, instrument}; use tracing::{debug, error, info, instrument};
mod config; mod config;
@ -129,7 +130,11 @@ fn handle_watch_event(
if let Some(new_path) = handle_file(&file, rules, remove_duplicates) { if let Some(new_path) = handle_file(&file, rules, remove_duplicates) {
maybe_link_target(&new_path, link_target); maybe_link_target(&new_path, link_target);
if notify && path != new_path { if notify && path != new_path {
show_notification(new_path); spawn_tokio(async {
if let Err(error) = show_notification(new_path).await {
error!(%error, "error with move notification");
}
})
} }
}; };
} }
@ -178,7 +183,7 @@ fn is_part(path: &Path) -> bool {
path.extension().and_then(|ext| ext.to_str()) == Some("part") path.extension().and_then(|ext| ext.to_str()) == Some("part")
} }
fn show_notification(source: PathBuf) { async fn show_notification(source: PathBuf) -> Result<(), ashpd::Error> {
debug!(file = %source.display(), "showing notification for file"); debug!(file = %source.display(), "showing notification for file");
let parent = source.parent().unwrap(); let parent = source.parent().unwrap();
match Notification::new() match Notification::new()
@ -191,58 +196,69 @@ fn show_notification(source: PathBuf) {
parent.display(), parent.display(),
parent.file_name().unwrap().to_string_lossy() parent.file_name().unwrap().to_string_lossy()
)) ))
.timeout(Duration::from_secs(10))
.hint(Hint::ActionIcons(true)) .hint(Hint::ActionIcons(true))
.hint(Hint::Category("transfer.complete".into())) .hint(Hint::Category("transfer.complete".into()))
.action("document-open", "Open") .action("document-open", "Open")
.action("folder-open", "Open Containing folder") .action("folder-open", "Open Containing folder")
.show() .show_async()
.await
{ {
Ok(notification) => { Ok(notification) => {
spawn(move || { let mut response = None;
notification.wait_for_action(|action| match action { let _ = timeout(
"document-open" => { Duration::from_secs(11),
if let Err(error) = open_file(&source) { notification.wait_for_action_async(|action| {
error!(%error, file = %source.display(), "failed to open file from notification"); if let ActionResponse::Custom(action) = action {
} response = Some(String::from(*action));
} }
"folder-open" => { }),
if let Err(error) = open_file(source.parent().unwrap()) { )
error!(%error, file = %source.display(), "failed to open parent folder from notification"); .await;
}
match response.as_deref() {
Some("document-open") => {
if let Err(error) = open_file(&source).await {
error!(%error, file = %source.display(), "failed to open file from notification");
} }
_ => (), }
}); Some("folder-open") => {
}); if let Err(error) = open_file(source.parent().unwrap()).await {
error!(%error, file = %source.display(), "failed to open parent folder from notification");
}
}
_ => {}
}
} }
Err(error) => { Err(error) => {
error!(%error, "Failed to show notification"); error!(%error, "Failed to show notification");
} }
} }
Ok(())
} }
fn open_file(path: impl AsRef<Path>) -> Result<(), ashpd::Error> { async fn open_file(path: impl AsRef<Path>) -> Result<(), ashpd::Error> {
let path = path.as_ref().to_owned(); let path = path.as_ref();
debug!(path = %path.display(), "sending open request"); debug!(path = %path.display(), "sending open request");
spawn_tokio_blocking(async move { match File::open(path) {
match File::open(&path) { Ok(fd) => {
Ok(fd) => { let result = if path.is_dir() {
let result = if path.is_dir() { let request = open_uri::OpenDirectoryRequest::default();
let request = open_uri::OpenDirectoryRequest::default(); request.send(&fd).await
request.send(&fd).await } else {
} else { let request = open_uri::OpenFileRequest::default().ask(false);
let request = open_uri::OpenFileRequest::default().ask(false); request.send_file(&fd).await
request.send_file(&fd).await };
};
result.inspect_err(|err| error!("Failed to send open request: {err}"))?; result.inspect_err(|err| error!("Failed to send open request: {err}"))?;
Ok(()) Ok(())
}
Err(err) => {
error!("Failed to open file: {err}");
Err(ashpd::Error::IO(err))
}
} }
}) Err(err) => {
error!("Failed to open file: {err}");
Err(ashpd::Error::IO(err))
}
}
} }
fn maybe_link_target(source: &Path, target: Option<&str>) { fn maybe_link_target(source: &Path, target: Option<&str>) {
@ -281,7 +297,7 @@ fn match_file(file: &FileInfo, rules: &[Rule]) -> Option<RuleResult> {
None None
} }
pub fn spawn_tokio_blocking<F>(fut: F) -> F::Output pub fn spawn_tokio<F>(fut: F)
where where
F: Future + Send + 'static, F: Future + Send + 'static,
F::Output: Send + 'static, F::Output: Send + 'static,
@ -292,10 +308,8 @@ where
.build() .build()
.expect("failed to build tokio runtime"); .expect("failed to build tokio runtime");
rt.block_on(fut) rt.block_on(fut);
}) });
.join()
.expect("fail for thread")
} }
#[instrument(skip_all, fields(file = file.path))] #[instrument(skip_all, fields(file = file.path))]