cloudpub_client/plugins/
plugin_trait.rs1use crate::config::{ClientConfig, ClientOpts};
2use crate::shell::SubProcess;
3use anyhow::{bail, Context, Result};
4use async_trait::async_trait;
5use cloudpub_common::fair_channel::FairSender;
6use cloudpub_common::protocol::message::Message;
7use cloudpub_common::protocol::{Break, ErrorInfo, ErrorKind, ServerEndpoint};
8use cloudpub_common::utils::is_tcp_port_available;
9use parking_lot::RwLock;
10use std::sync::Arc;
11use tokio::sync::mpsc;
12use tokio::task::JoinHandle;
13use tokio::time::{self, Duration, Instant};
14use tracing::{debug, error};
15
16fn encode_client_config(endpoint: &ServerEndpoint) -> Vec<u8> {
19 use cloudpub_common::prost::Message as ProstMessage;
20 endpoint
21 .client
22 .as_ref()
23 .map(|c| c.encode_to_vec())
24 .unwrap_or_default()
25}
26
27#[async_trait]
28pub trait Plugin: Send + Sync {
29 fn name(&self) -> &'static str;
31
32 async fn setup(
34 &self,
35 config: &Arc<RwLock<ClientConfig>>,
36 opts: &ClientOpts,
37 command_rx: &mut mpsc::Receiver<Message>,
38 result_tx: &mpsc::Sender<Message>,
39 ) -> Result<()>;
40
41 async fn publish(
43 &self,
44 endpoint: &ServerEndpoint,
45 config: &Arc<RwLock<ClientConfig>>,
46 opts: &ClientOpts,
47 result_tx: &mpsc::Sender<Message>,
48 ) -> Result<SubProcess>;
49}
50
51pub struct PluginHandle {
52 guid: String,
53 client_config: Vec<u8>,
56 server: Arc<RwLock<Option<SubProcess>>>,
57 cancel_tx: mpsc::Sender<Message>,
58 task: JoinHandle<()>,
59}
60
61impl PluginHandle {
62 pub fn spawn(
63 plugin: Arc<dyn Plugin>,
64 mut endpoint: ServerEndpoint,
65 config: Arc<RwLock<ClientConfig>>,
66 opts: ClientOpts,
67 to_server_tx: FairSender<Message>,
68 ) -> Self {
69 let guid = endpoint.guid.clone();
70 let client_config = encode_client_config(&endpoint);
71 let guid_for_task = guid.clone();
72 let server = Arc::new(RwLock::new(None));
73 let server_task = server.clone();
74
75 let (cancel_tx, mut cancel_rx) = mpsc::channel::<Message>(1);
77 let (proc_event_tx, mut proc_event_rx) = mpsc::channel::<Message>(1024);
78
79 let task = tokio::spawn(async move {
80 tokio::spawn({
82 let mut endpoint = endpoint.clone();
83 let to_server_tx = to_server_tx.clone();
84 let guid_for_task = guid_for_task.clone();
85 async move {
86 use tokio::time::{Duration, Instant};
87
88 let mut last_progress_time = Instant::now() - Duration::from_secs(1); while let Some(mut msg) = proc_event_rx.recv().await {
91 let should_send = match &mut msg {
92 Message::Progress(progress_info) => {
93 progress_info.guid = guid_for_task.clone();
94
95 let now = Instant::now();
96 if progress_info.current == 0
98 || progress_info.current >= progress_info.total
99 || now.duration_since(last_progress_time)
100 >= Duration::from_secs(1)
101 {
102 last_progress_time = now;
103 true
104 } else {
105 false
106 }
107 }
108 _ => true, };
110
111 if should_send {
112 to_server_tx.send(msg.clone()).await.ok();
113 }
114 }
115
116 endpoint.status = Some("offline".to_string());
117 to_server_tx
118 .send(Message::EndpointStatus(endpoint.clone()))
119 .await
120 .ok();
121 }
122 });
123
124 endpoint.status = Some("waiting".to_string());
126 let _ = to_server_tx
127 .send(Message::EndpointStatus(endpoint.clone()))
128 .await;
129
130 let res: Result<()> = async {
131 plugin
133 .setup(&config, &opts, &mut cancel_rx, &proc_event_tx)
134 .await
135 .context("Failed to setup plugin")?;
136
137 let server_process = plugin
138 .publish(&endpoint, &config, &opts, &proc_event_tx)
139 .await
140 .context("Failed to publish plugin service")?;
141
142 let now = Instant::now();
144 while is_tcp_port_available("127.0.0.1", server_process.port)
145 .await
146 .context("Check port availability")?
147 {
148 if server_process.result.read().is_err() {
149 return Ok(()); }
151
152 if now.elapsed() > Duration::from_secs(60) {
153 bail!("{}", crate::t!("error-start-server"));
154 }
155 debug!(
156 "Waiting for server to start on port {}",
157 server_process.port
158 );
159 time::sleep(Duration::from_secs(1)).await;
160 }
161
162 *server_task.write() = Some(server_process);
163 Ok(())
164 }
165 .await;
166
167 match res {
168 Ok(()) => {
169 endpoint.status = Some("online".into());
170 let _ = to_server_tx.send(Message::EndpointStatus(endpoint)).await;
171 }
172 Err(e) => {
173 error!("Error handling endpoint {}: {:#}", &guid_for_task, e);
174 let _ = to_server_tx
175 .send(Message::Error(ErrorInfo {
176 kind: ErrorKind::PublishFailed.into(),
177 message: e.to_string(),
178 guid: guid_for_task,
179 }))
180 .await;
181 }
182 }
183 });
184
185 Self {
186 guid,
187 client_config,
188 server,
189 cancel_tx,
190 task,
191 }
192 }
193
194 pub fn same_config(&self, endpoint: &ServerEndpoint) -> bool {
199 self.client_config == encode_client_config(endpoint)
200 }
201
202 pub fn is_healthy(&self) -> bool {
206 if !self.task.is_finished() {
207 return true;
208 }
209 match self.server.read().as_ref() {
210 Some(process) => process.result.read().is_ok(),
211 None => false,
212 }
213 }
214
215 pub fn port(&self) -> Option<u16> {
216 self.server.read().as_ref().map(|s| s.port)
217 }
218
219 pub fn guid(&self) -> &str {
220 &self.guid
221 }
222
223 pub fn send_break(&self) {
224 let _ = self.cancel_tx.try_send(Message::Break(Break {
225 guid: self.guid.clone(),
226 }));
227 }
228}
229
230impl Drop for PluginHandle {
231 fn drop(&mut self) {
232 self.task.abort();
234 let _ = self.cancel_tx.try_send(Message::Break(Break {
236 guid: self.guid.clone(),
237 }));
238 }
239}