Skip to content

Commit 983d5f0

Browse files
committed
Listen to podman events
1 parent a378e6e commit 983d5f0

File tree

1 file changed

+146
-1
lines changed

1 file changed

+146
-1
lines changed

src/store/root_store.rs

Lines changed: 146 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,98 @@ use crate::distrobox::CreateArgs;
2121
use crate::distrobox::Distrobox;
2222
use crate::distrobox::Status;
2323
use crate::distrobox_task::DistroboxTask;
24-
use crate::fakers::{Command, CommandRunner, FdMode};
24+
use crate::fakers::{Child, Command, CommandRunner, FdMode};
2525
use crate::gtk_utils::{reconcile_list_by_key, TypedListStore};
2626
use crate::query::Query;
2727
use crate::supported_terminals::{Terminal, TerminalRepository};
2828
use crate::tagged_object::TaggedObject;
2929

30+
use std::pin::Pin;
31+
use std::task::{Context as TaskContext, Poll};
32+
use serde::Deserialize;
33+
use std::collections::HashMap;
34+
35+
/// Podman event structure
36+
#[derive(Debug, Clone, Deserialize)]
37+
#[serde(rename_all = "PascalCase")]
38+
pub struct PodmanEvent {
39+
#[serde(rename = "ID")]
40+
pub id: Option<String>,
41+
pub name: Option<String>,
42+
pub status: Option<String>,
43+
#[serde(rename = "Type")]
44+
pub event_type: Option<String>,
45+
pub attributes: Option<HashMap<String, String>>,
46+
}
47+
48+
impl PodmanEvent {
49+
/// Check if this event is for a distrobox container
50+
pub fn is_distrobox(&self) -> bool {
51+
self.attributes
52+
.as_ref()
53+
.and_then(|attrs| attrs.get("manager"))
54+
.map(|manager| manager == "distrobox")
55+
.unwrap_or(false)
56+
}
57+
58+
/// Check if this is a container event
59+
pub fn is_container_event(&self) -> bool {
60+
self.event_type
61+
.as_ref()
62+
.map(|t| t == "container")
63+
.unwrap_or(false)
64+
}
65+
}
66+
67+
/// Stream wrapper for podman events
68+
pub struct PodmanEventsStream {
69+
lines: Option<futures::io::Lines<futures::io::BufReader<Box<dyn futures::io::AsyncRead + Send + Unpin>>>>,
70+
_child: Option<Box<dyn Child + Send>>,
71+
}
72+
73+
impl Stream for PodmanEventsStream {
74+
type Item = Result<String, std::io::Error>;
75+
76+
fn poll_next(mut self: Pin<&mut Self>, cx: &mut TaskContext<'_>) -> Poll<Option<Self::Item>> {
77+
if let Some(ref mut lines) = self.lines {
78+
Pin::new(lines).poll_next(cx)
79+
} else {
80+
Poll::Ready(None)
81+
}
82+
}
83+
}
84+
85+
/// Listen to podman events and return a stream of event lines
86+
pub fn listen_podman_events(
87+
command_runner: CommandRunner,
88+
) -> Result<PodmanEventsStream, std::io::Error> {
89+
use futures::io::{AsyncBufReadExt, BufReader};
90+
91+
// Create the podman events command
92+
let mut cmd = Command::new("podman");
93+
cmd.arg("events");
94+
cmd.arg("--format");
95+
cmd.arg("json");
96+
cmd.stdout = FdMode::Pipe;
97+
cmd.stderr = FdMode::Pipe;
98+
99+
// Spawn the command
100+
let mut child = command_runner.spawn(cmd)?;
101+
102+
// Get stdout and create a buffered reader
103+
let stdout = child.take_stdout().ok_or_else(|| {
104+
std::io::Error::new(std::io::ErrorKind::Other, "No stdout available")
105+
})?;
106+
107+
let bufread = BufReader::new(stdout);
108+
let lines = bufread.lines();
109+
110+
Ok(PodmanEventsStream {
111+
lines: Some(lines),
112+
_child: Some(child),
113+
})
114+
}
115+
30116
mod imp {
31117
use crate::query::Query;
32118

@@ -150,6 +236,7 @@ impl RootStore {
150236
}
151237

152238
this.load_containers();
239+
this.start_listening_podman_events();
153240
this
154241
}
155242

@@ -209,6 +296,64 @@ impl RootStore {
209296
},
210297
);
211298
}
299+
300+
/// Start listening to podman events and auto-refresh container list for distrobox events
301+
pub fn start_listening_podman_events(&self) {
302+
let this = self.clone();
303+
let command_runner = self.command_runner();
304+
305+
glib::MainContext::ref_thread_default().spawn_local(async move {
306+
info!("Starting podman events listener");
307+
308+
let stream = match listen_podman_events(command_runner) {
309+
Ok(stream) => stream,
310+
Err(e) => {
311+
warn!("Failed to start podman events listener: {}", e);
312+
return;
313+
}
314+
};
315+
316+
// Process events
317+
stream
318+
.for_each(|line_result| {
319+
let this = this.clone();
320+
async move {
321+
match line_result {
322+
Ok(line) => {
323+
// Parse the JSON event
324+
match serde_json::from_str::<PodmanEvent>(&line) {
325+
Ok(event) => {
326+
debug!(
327+
"Received podman event: type={:?}, status={:?}, name={:?}",
328+
event.event_type, event.status, event.name
329+
);
330+
331+
// Only refresh if this is a distrobox container event
332+
if event.is_container_event() && event.is_distrobox() {
333+
info!(
334+
"Distrobox container event detected ({}), refreshing container list",
335+
event.status.as_deref().unwrap_or("unknown")
336+
);
337+
this.load_containers();
338+
}
339+
}
340+
Err(e) => {
341+
debug!("Failed to parse podman event JSON: {} - Line: {}", e, line);
342+
}
343+
}
344+
}
345+
Err(e) => {
346+
error!("Error reading podman event: {}", e);
347+
}
348+
}
349+
}
350+
})
351+
.await;
352+
353+
warn!("Podman events listener stopped");
354+
});
355+
}
356+
212357
pub fn selected_container_name(&self) -> Option<String> {
213358
self.selected_container().map(|c| c.name())
214359
}

0 commit comments

Comments
 (0)