feat(script): new watch mode

Resolves #30
This commit is contained in:
Jake Stanger 2022-11-06 17:39:15 +00:00
parent 1c032ae8e3
commit 73158c2fce
No known key found for this signature in database
GPG Key ID: C51FC8F9CB0BEA61
3 changed files with 100 additions and 15 deletions

10
Cargo.lock generated
View File

@ -1948,6 +1948,15 @@ dependencies = [
"lazy_static", "lazy_static",
] ]
[[package]]
name = "signal-hook-registry"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e51e73328dc4ac0c7ccbda3a494dfa03df1de2f46018127f60c693f2648455b0"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.7" version = "0.4.7"
@ -2209,6 +2218,7 @@ dependencies = [
"mio", "mio",
"num_cpus", "num_cpus",
"pin-project-lite", "pin-project-lite",
"signal-hook-registry",
"socket2", "socket2",
"tokio-macros", "tokio-macros",
"winapi", "winapi",

View File

@ -10,7 +10,7 @@ derive_builder = "0.11.2"
gtk = "0.16.0" gtk = "0.16.0"
gtk-layer-shell = "0.5.0" gtk-layer-shell = "0.5.0"
glib = "0.16.2" glib = "0.16.2"
tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time"] } tokio = { version = "1.21.2", features = ["macros", "rt-multi-thread", "time", "process"] }
tracing = "0.1.37" tracing = "0.1.37"
tracing-subscriber = { version = "0.3.16", features = ["env-filter"] } tracing-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-error = "0.2.0" tracing-error = "0.2.0"

View File

@ -1,23 +1,41 @@
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext}; use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::script::exec_command; use crate::script::exec_command;
use color_eyre::Result; use color_eyre::{Help, Report, Result};
use gtk::prelude::*; use gtk::prelude::*;
use gtk::Label; use gtk::Label;
use serde::Deserialize; use serde::Deserialize;
use tokio::spawn; use std::process::Stdio;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use tokio::sync::mpsc::{Receiver, Sender}; use tokio::sync::mpsc::{Receiver, Sender};
use tokio::time::sleep; use tokio::time::sleep;
use tokio::{select, spawn};
use tracing::error; use tracing::error;
#[derive(Debug, Deserialize, Clone, Copy)]
#[serde(rename_all = "kebab-case")]
enum Mode {
Poll,
Watch,
}
#[derive(Debug, Deserialize, Clone)] #[derive(Debug, Deserialize, Clone)]
pub struct ScriptModule { pub struct ScriptModule {
/// Path to script to execute. /// Path to script to execute.
path: String, path: String,
/// Script execution mode
#[serde(default = "default_mode")]
mode: Mode,
/// Time in milliseconds between executions. /// Time in milliseconds between executions.
#[serde(default = "default_interval")] #[serde(default = "default_interval")]
interval: u64, interval: u64,
} }
/// `Mode::Poll`
const fn default_mode() -> Mode {
Mode::Poll
}
/// 5000ms /// 5000ms
const fn default_interval() -> u64 { const fn default_interval() -> u64 {
5000 5000
@ -35,7 +53,9 @@ impl Module<Label> for ScriptModule {
) -> Result<()> { ) -> Result<()> {
let interval = self.interval; let interval = self.interval;
let path = self.path.clone(); let path = self.path.clone();
spawn(async move {
match self.mode {
Mode::Poll => spawn(async move {
loop { loop {
match exec_command(&path) { match exec_command(&path) {
Ok(stdout) => tx Ok(stdout) => tx
@ -47,7 +67,62 @@ impl Module<Label> for ScriptModule {
sleep(tokio::time::Duration::from_millis(interval)).await; sleep(tokio::time::Duration::from_millis(interval)).await;
} }
}); }),
Mode::Watch => spawn(async move {
loop {
let mut handle = Command::new("sh")
.args(["-c", &path])
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.stdin(Stdio::null())
.spawn()
.expect("Failed to spawn process");
let mut stdout_lines = BufReader::new(
handle
.stdout
.take()
.expect("Failed to take script handle stdout"),
)
.lines();
let mut stderr_lines = BufReader::new(
handle
.stderr
.take()
.expect("Failed to take script handle stderr"),
)
.lines();
loop {
select! {
_ = handle.wait() => break,
Ok(Some(line)) = stdout_lines.next_line() => {
tx.send(ModuleUpdateEvent::Update(line.to_string()))
.await
.expect("Failed to send stdout");
}
Ok(Some(line)) = stderr_lines.next_line() => {
error!("{:?}", Report::msg(line)
.wrap_err("Watched script error:")
.suggestion("Check the path to your script")
.suggestion("Check the script for errors")
.suggestion("If you expect the script to write to stderr, consider redirecting its output to /dev/null to suppress these messages")
)
}
}
}
while let Ok(Some(line)) = stdout_lines.next_line().await {
tx.send(ModuleUpdateEvent::Update(line.to_string()))
.await
.expect("Failed to send stdout");
}
sleep(tokio::time::Duration::from_millis(interval)).await;
}
}),
};
Ok(()) Ok(())
} }