@@ -21,12 +21,98 @@ use crate::distrobox::CreateArgs;
2121use crate :: distrobox:: Distrobox ;
2222use crate :: distrobox:: Status ;
2323use crate :: distrobox_task:: DistroboxTask ;
24- use crate :: fakers:: { Command , CommandRunner , FdMode } ;
24+ use crate :: fakers:: { Child , Command , CommandRunner , FdMode } ;
2525use crate :: gtk_utils:: { reconcile_list_by_key, TypedListStore } ;
2626use crate :: query:: Query ;
2727use crate :: supported_terminals:: { Terminal , TerminalRepository } ;
2828use 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+
30116mod 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