feat(robin): M1 System Presence — journald/kmsg/inotify watcher, pattern classifier, tray badge, chat panel #2
1 changed files with 68 additions and 3 deletions
|
|
@ -1,6 +1,71 @@
|
||||||
use super::SystemEvent;
|
use super::{EventSource, SystemEvent, now_unix};
|
||||||
|
use tokio::io::{AsyncBufReadExt, BufReader};
|
||||||
|
use tokio::process::Command;
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
pub async fn watch(_tx: mpsc::Sender<SystemEvent>) {
|
pub async fn watch(tx: mpsc::Sender<SystemEvent>) {
|
||||||
// implemented in Task 6
|
let mut child = match Command::new("journalctl")
|
||||||
|
.args(["--follow", "--output=json", "--lines=0"])
|
||||||
|
.stdout(std::process::Stdio::piped())
|
||||||
|
.spawn()
|
||||||
|
{
|
||||||
|
Ok(c) => c,
|
||||||
|
Err(e) => {
|
||||||
|
log::error!("journald watcher: failed to spawn journalctl: {e}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let stdout = match child.stdout.take() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => return,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut lines = BufReader::new(stdout).lines();
|
||||||
|
while let Ok(Some(line)) = lines.next_line().await {
|
||||||
|
if let Some(msg) = extract_message(&line) {
|
||||||
|
let _ = tx
|
||||||
|
.send(SystemEvent {
|
||||||
|
source: EventSource::Journald,
|
||||||
|
raw_line: msg,
|
||||||
|
timestamp: now_unix(),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn extract_message(line: &str) -> Option<String> {
|
||||||
|
let json: serde_json::Value = serde_json::from_str(line).ok()?;
|
||||||
|
let msg = json.get("MESSAGE")?.as_str()?;
|
||||||
|
if msg.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
Some(msg.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_message_from_journald_json() {
|
||||||
|
let line = r#"{"MESSAGE":"AUR build failed for foo","PRIORITY":"3","_COMM":"makepkg"}"#;
|
||||||
|
assert_eq!(
|
||||||
|
extract_message(line),
|
||||||
|
Some("AUR build failed for foo".to_string())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_message_missing_returns_none() {
|
||||||
|
let line = r#"{"PRIORITY":"6","_COMM":"systemd"}"#;
|
||||||
|
assert_eq!(extract_message(line), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn extract_message_empty_skipped() {
|
||||||
|
let line = r#"{"MESSAGE":"","PRIORITY":"6"}"#;
|
||||||
|
assert_eq!(extract_message(line), None);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue