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

View File

@ -10,7 +10,7 @@ derive_builder = "0.11.2"
gtk = "0.16.0"
gtk-layer-shell = "0.5.0"
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-subscriber = { version = "0.3.16", features = ["env-filter"] }
tracing-error = "0.2.0"

View File

@ -1,23 +1,41 @@
use crate::modules::{Module, ModuleInfo, ModuleUpdateEvent, ModuleWidget, WidgetContext};
use crate::script::exec_command;
use color_eyre::Result;
use color_eyre::{Help, Report, Result};
use gtk::prelude::*;
use gtk::Label;
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::time::sleep;
use tokio::{select, spawn};
use tracing::error;
#[derive(Debug, Deserialize, Clone, Copy)]
#[serde(rename_all = "kebab-case")]
enum Mode {
Poll,
Watch,
}
#[derive(Debug, Deserialize, Clone)]
pub struct ScriptModule {
/// Path to script to execute.
path: String,
/// Script execution mode
#[serde(default = "default_mode")]
mode: Mode,
/// Time in milliseconds between executions.
#[serde(default = "default_interval")]
interval: u64,
}
/// `Mode::Poll`
const fn default_mode() -> Mode {
Mode::Poll
}
/// 5000ms
const fn default_interval() -> u64 {
5000
@ -35,19 +53,76 @@ impl Module<Label> for ScriptModule {
) -> Result<()> {
let interval = self.interval;
let path = self.path.clone();
spawn(async move {
loop {
match exec_command(&path) {
Ok(stdout) => tx
.send(ModuleUpdateEvent::Update(stdout))
.await
.expect("Failed to send stdout"),
Err(err) => error!("{:?}", err),
}
sleep(tokio::time::Duration::from_millis(interval)).await;
}
});
match self.mode {
Mode::Poll => spawn(async move {
loop {
match exec_command(&path) {
Ok(stdout) => tx
.send(ModuleUpdateEvent::Update(stdout))
.await
.expect("Failed to send stdout"),
Err(err) => error!("{:?}", err),
}
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(())
}