feat(robin): M1 System Presence — journald/kmsg/inotify watcher, pattern classifier, tray badge, chat panel #2
6 changed files with 105 additions and 39 deletions
|
|
@ -24,7 +24,7 @@ pub fn run() {
|
||||||
})
|
})
|
||||||
.setup(|app| {
|
.setup(|app| {
|
||||||
tray::build_tray(&app.handle())?;
|
tray::build_tray(&app.handle())?;
|
||||||
watcher::spawn();
|
watcher::spawn(std::collections::HashMap::new());
|
||||||
Ok(())
|
Ok(())
|
||||||
})
|
})
|
||||||
.invoke_handler(tauri::generate_handler![
|
.invoke_handler(tauri::generate_handler![
|
||||||
|
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/// System event watcher — M1 implementation.
|
|
||||||
///
|
|
||||||
/// M0 stub: defines the types and the spawn interface so the rest of the app
|
|
||||||
/// can wire up event handling now. Actual journald/dmesg reading lands in M1.
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(rename_all = "snake_case")]
|
|
||||||
pub enum EventSeverity {
|
|
||||||
Info,
|
|
||||||
Warn,
|
|
||||||
Crit,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
|
||||||
pub enum EventSource {
|
|
||||||
Journald,
|
|
||||||
Kmsg,
|
|
||||||
AppLog { app: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Serialize)]
|
|
||||||
pub struct SystemEvent {
|
|
||||||
pub source: EventSource,
|
|
||||||
pub raw_line: String,
|
|
||||||
pub timestamp: u64,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Starts the background watcher task.
|
|
||||||
/// M0: no-op placeholder — returns immediately.
|
|
||||||
/// M1: spawns a tokio task reading journald + dmesg and emitting events.
|
|
||||||
pub fn spawn() {
|
|
||||||
// TODO(M1): spawn tokio::task reading journald via sd-journal crate
|
|
||||||
// TODO(M1): spawn dmesg poller for kernel messages
|
|
||||||
// TODO(M1): emit SystemEvent via tauri app_handle.emit()
|
|
||||||
log::info!("watcher: stub — no-op until M1");
|
|
||||||
}
|
|
||||||
7
src-tauri/src/watcher/inotify.rs
Normal file
7
src-tauri/src/watcher/inotify.rs
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
use super::SystemEvent;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
pub async fn watch(_log_paths: HashMap<String, String>, _tx: mpsc::Sender<SystemEvent>) {
|
||||||
|
// implemented in Task 8
|
||||||
|
}
|
||||||
6
src-tauri/src/watcher/journald.rs
Normal file
6
src-tauri/src/watcher/journald.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
use super::SystemEvent;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
pub async fn watch(_tx: mpsc::Sender<SystemEvent>) {
|
||||||
|
// implemented in Task 6
|
||||||
|
}
|
||||||
6
src-tauri/src/watcher/kmsg.rs
Normal file
6
src-tauri/src/watcher/kmsg.rs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
use super::SystemEvent;
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
pub async fn watch(_tx: mpsc::Sender<SystemEvent>) {
|
||||||
|
// implemented in Task 7
|
||||||
|
}
|
||||||
85
src-tauri/src/watcher/mod.rs
Normal file
85
src-tauri/src/watcher/mod.rs
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
pub mod inotify;
|
||||||
|
pub mod journald;
|
||||||
|
pub mod kmsg;
|
||||||
|
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::time::{SystemTime, UNIX_EPOCH};
|
||||||
|
use tokio::sync::mpsc;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(rename_all = "snake_case")]
|
||||||
|
pub enum EventSeverity {
|
||||||
|
Info,
|
||||||
|
Warn,
|
||||||
|
Crit,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||||
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
|
pub enum EventSource {
|
||||||
|
Journald,
|
||||||
|
Kmsg,
|
||||||
|
AppLog { app: String },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Serialize)]
|
||||||
|
pub struct SystemEvent {
|
||||||
|
pub source: EventSource,
|
||||||
|
pub raw_line: String,
|
||||||
|
pub timestamp: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn now_unix() -> u64 {
|
||||||
|
SystemTime::now()
|
||||||
|
.duration_since(UNIX_EPOCH)
|
||||||
|
.unwrap_or_default()
|
||||||
|
.as_secs()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Spawns all watcher tasks. Returns the receiver end of the event channel.
|
||||||
|
/// `log_paths` comes from the loaded PatternFile.
|
||||||
|
pub fn spawn(log_paths: HashMap<String, String>) -> mpsc::Receiver<SystemEvent> {
|
||||||
|
let (tx, rx) = mpsc::channel::<SystemEvent>(256);
|
||||||
|
|
||||||
|
let tx_j = tx.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
journald::watch(tx_j).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
let tx_k = tx.clone();
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
kmsg::watch(tx_k).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
tauri::async_runtime::spawn(async move {
|
||||||
|
inotify::watch(log_paths, tx).await;
|
||||||
|
});
|
||||||
|
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn event_source_can_be_cloned() {
|
||||||
|
let s = EventSource::AppLog {
|
||||||
|
app: "steam".into(),
|
||||||
|
};
|
||||||
|
let _ = s.clone();
|
||||||
|
assert!(matches!(s, EventSource::AppLog { .. }));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn system_event_constructed() {
|
||||||
|
let e = SystemEvent {
|
||||||
|
source: EventSource::Journald,
|
||||||
|
raw_line: "test line".into(),
|
||||||
|
timestamp: now_unix(),
|
||||||
|
};
|
||||||
|
assert_eq!(e.raw_line, "test line");
|
||||||
|
assert!(e.timestamp > 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue