Rust · 3478 bytes Raw Blame History
1 //! Request tracking for portal dialogs.
2 //!
3 //! Each portal request gets a unique handle path and can be cancelled.
4
5 use std::collections::HashMap;
6 use std::sync::Arc;
7 use tokio::process::Child;
8 use tokio::sync::Mutex;
9 use zbus::zvariant::{ObjectPath, OwnedObjectPath, Value};
10 use zbus::{interface, fdo};
11
12 /// A pending file chooser request.
13 pub struct PendingRequest {
14 /// The child process running garfield.
15 pub child: Child,
16 /// Whether the request has been cancelled.
17 pub cancelled: bool,
18 }
19
20 /// Manages pending requests.
21 #[derive(Clone, Default)]
22 pub struct RequestManager {
23 requests: Arc<Mutex<HashMap<OwnedObjectPath, PendingRequest>>>,
24 }
25
26 impl RequestManager {
27 pub fn new() -> Self {
28 Self::default()
29 }
30
31 /// Add a new pending request.
32 pub async fn add(&self, handle: OwnedObjectPath, child: Child) {
33 let mut requests = self.requests.lock().await;
34 requests.insert(handle, PendingRequest {
35 child,
36 cancelled: false,
37 });
38 }
39
40 /// Remove a request and return it.
41 pub async fn remove(&self, handle: &ObjectPath<'_>) -> Option<PendingRequest> {
42 let mut requests = self.requests.lock().await;
43 requests.remove(&handle.to_owned())
44 }
45
46 /// Cancel a request by killing the child process.
47 pub async fn cancel(&self, handle: &ObjectPath<'_>) -> bool {
48 let mut requests = self.requests.lock().await;
49 if let Some(request) = requests.get_mut(&handle.to_owned()) {
50 request.cancelled = true;
51 let _ = request.child.kill().await;
52 true
53 } else {
54 false
55 }
56 }
57
58 /// Check if a request was cancelled.
59 #[allow(dead_code)]
60 pub async fn is_cancelled(&self, handle: &ObjectPath<'_>) -> bool {
61 let requests = self.requests.lock().await;
62 requests.get(&handle.to_owned())
63 .map(|r| r.cancelled)
64 .unwrap_or(false)
65 }
66 }
67
68 /// Request object that can be exported on D-Bus for cancellation.
69 pub struct Request {
70 handle: OwnedObjectPath,
71 manager: RequestManager,
72 }
73
74 impl Request {
75 pub fn new(handle: OwnedObjectPath, manager: RequestManager) -> Self {
76 Self { handle, manager }
77 }
78 }
79
80 #[interface(name = "org.freedesktop.impl.portal.Request")]
81 impl Request {
82 /// Close/cancel the request.
83 async fn close(&self) -> fdo::Result<()> {
84 // Log with backtrace info
85 tracing::warn!("Close() called on request: {}", self.handle);
86 let cancelled = self.manager.cancel(&self.handle.as_ref()).await;
87 tracing::warn!("Cancel result: {} (true = child was killed)", cancelled);
88 Ok(())
89 }
90 }
91
92 /// Response codes for portal dialogs.
93 #[repr(u32)]
94 pub enum ResponseCode {
95 /// User accepted the dialog.
96 Success = 0,
97 /// User cancelled the dialog.
98 Cancelled = 1,
99 /// An error occurred.
100 Error = 2,
101 }
102
103 /// Build response results for file chooser.
104 pub fn build_file_chooser_response(
105 paths: Vec<String>,
106 ) -> HashMap<String, Value<'static>> {
107 use zbus::zvariant::Array;
108
109 let mut results = HashMap::new();
110
111 // Convert paths to file:// URIs as a proper string array (as)
112 let uris: Vec<String> = paths
113 .into_iter()
114 .map(|p| format!("file://{}", p))
115 .collect();
116
117 // Create array with proper 's' (string) signature
118 let uri_array = Array::from(uris);
119 results.insert("uris".to_string(), Value::Array(uri_array));
120 results
121 }
122