1use std::collections::HashMap;
6
7use async_utils::hanging_get::server;
8use fidl_fuchsia_settings::{
9 LightError, LightGroup as FidlLightGroup, LightRequest, LightRequestStream, LightState,
10 LightWatchLightGroupResponder, LightWatchLightGroupsResponder,
11};
12use fuchsia_async as fasync;
13use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
14use futures::channel::oneshot;
15use futures::StreamExt;
16
17use crate::light_controller::{
18 LightController, LightError as ControllerLightError, Request, ARG_NAME,
19};
20use crate::types::{LightGroup, LightInfo};
21use settings_common::inspect::event::{
22 RequestType, ResponseType, UsagePublisher, UsageResponsePublisher,
23};
24
25pub(crate) type SubscriberObject<T> = (UsageResponsePublisher<LightInfo>, T);
26pub(crate) type InfoSubscriberObject = SubscriberObject<LightWatchLightGroupsResponder>;
27pub(crate) type GroupSubscriberObject = SubscriberObject<LightWatchLightGroupResponder>;
28
29type InfoHangingFn = Box<dyn Fn(&LightInfo, InfoSubscriberObject) -> bool>;
30pub(super) type InfoHangingGet = server::HangingGet<LightInfo, InfoSubscriberObject, InfoHangingFn>;
31pub(super) type InfoPublisher = server::Publisher<LightInfo, InfoSubscriberObject, InfoHangingFn>;
32pub(super) type InfoSubscriber = server::Subscriber<LightInfo, InfoSubscriberObject, InfoHangingFn>;
33
34type GroupHangingFn = Box<dyn Fn(&LightGroup, GroupSubscriberObject) -> bool>;
35pub(super) type GroupHangingGet =
36 server::HangingGet<LightGroup, GroupSubscriberObject, GroupHangingFn>;
37pub(super) type GroupPublisher =
38 server::Publisher<LightGroup, GroupSubscriberObject, GroupHangingFn>;
39pub(super) type GroupSubscriber =
40 server::Subscriber<LightGroup, GroupSubscriberObject, GroupHangingFn>;
41
42pub struct LightFidlHandler {
43 info_hanging_get: InfoHangingGet,
44 group_hanging_gets: HashMap<String, GroupHangingGet>,
45 controller_tx: UnboundedSender<Request>,
46 usage_publisher: UsagePublisher<LightInfo>,
47}
48
49impl LightFidlHandler {
50 pub(crate) fn new(
51 light_controller: &mut LightController,
52 usage_publisher: UsagePublisher<LightInfo>,
53 initial_value: LightInfo,
54 ) -> (Self, UnboundedReceiver<Request>) {
55 let (info_hanging_get, group_hanging_gets) = Self::build_hanging_gets(initial_value);
56 light_controller.register_publishers(
57 info_hanging_get.new_publisher(),
58 group_hanging_gets
59 .iter()
60 .map(|(key, hanging_get)| (key.clone(), hanging_get.new_publisher()))
61 .collect(),
62 );
63 let (controller_tx, controller_rx) = mpsc::unbounded();
64 (
65 Self { info_hanging_get, group_hanging_gets, controller_tx, usage_publisher },
66 controller_rx,
67 )
68 }
69
70 pub(crate) fn build_hanging_gets(
71 info: LightInfo,
72 ) -> (InfoHangingGet, HashMap<String, GroupHangingGet>) {
73 let group_hanging_gets: HashMap<_, _> = info
74 .light_groups
75 .clone()
76 .into_iter()
77 .map(|(key, group)| {
78 (
79 key,
80 GroupHangingGet::new(
81 group,
82 Box::new(|group, (usage_responder, responder)| {
83 usage_responder.respond(format!("{group:?}"), ResponseType::OkSome);
84 if let Err(e) = responder.send(&FidlLightGroup::from(group.clone())) {
85 log::warn!("Failed to respond to watch light group request: {e:?}");
86 return false;
87 }
88 true
89 }),
90 ),
91 )
92 })
93 .collect();
94 let info_hanging_get: InfoHangingGet = InfoHangingGet::new(
95 info,
96 Box::new(|info, (usage_responder, responder)| {
97 usage_responder.respond(format!("{info:?}"), ResponseType::OkSome);
98 if let Err(e) = responder.send(&Vec::<FidlLightGroup>::from(info)) {
99 log::warn!("Failed to respond to watch light groups request: {e:?}");
100 return false;
101 }
102 true
103 }),
104 );
105 (info_hanging_get, group_hanging_gets)
106 }
107
108 pub fn handle_stream(&mut self, mut stream: LightRequestStream) {
109 let request_handler = RequestHandler {
110 info_subscriber: self.info_hanging_get.new_subscriber(),
111 group_subscribers: self
112 .group_hanging_gets
113 .iter_mut()
114 .map(|(key, hanging_get)| (key.clone(), hanging_get.new_subscriber()))
115 .collect::<HashMap<_, _>>(),
116 controller_tx: self.controller_tx.clone(),
117 usage_publisher: self.usage_publisher.clone(),
118 };
119 fasync::Task::local(async move {
120 while let Some(Ok(request)) = stream.next().await {
121 request_handler.handle_request(request).await;
122 }
123 })
124 .detach();
125 }
126}
127
128#[derive(Debug)]
129enum HandlerError {
130 AlreadySubscribed,
131 NotFound,
132 ControllerStopped,
133 Controller(ControllerLightError),
134}
135
136impl From<&HandlerError> for ResponseType {
137 fn from(error: &HandlerError) -> Self {
138 match error {
139 HandlerError::AlreadySubscribed => ResponseType::AlreadySubscribed,
140 HandlerError::NotFound => ResponseType::InvalidArgument,
141 HandlerError::ControllerStopped => ResponseType::UnexpectedError,
142 HandlerError::Controller(e) => ResponseType::from(e),
143 }
144 }
145}
146
147impl From<HandlerError> for LightError {
148 fn from(error: HandlerError) -> Self {
149 if let HandlerError::Controller(ControllerLightError::InvalidArgument(argument, _)) = error
150 {
151 if ARG_NAME == argument {
152 LightError::InvalidName
153 } else {
154 LightError::InvalidValue
155 }
156 } else {
157 LightError::Failed
158 }
159 }
160}
161
162struct RequestHandler {
163 info_subscriber: InfoSubscriber,
164 group_subscribers: HashMap<String, GroupSubscriber>,
165 controller_tx: UnboundedSender<Request>,
166 usage_publisher: UsagePublisher<LightInfo>,
167}
168
169impl RequestHandler {
170 async fn handle_request(&self, request: LightRequest) {
171 match request {
172 LightRequest::WatchLightGroups { responder } => {
173 let usage_res =
174 self.usage_publisher.request("WatchLightGroups".to_string(), RequestType::Get);
175 if let Err((usage_res, responder)) =
176 self.info_subscriber.register2((usage_res, responder))
177 {
178 let e = HandlerError::AlreadySubscribed;
179 usage_res.respond(format!("Err({e:?})"), ResponseType::from(&e));
180 drop(responder);
181 }
182 }
183 LightRequest::WatchLightGroup { name, responder } => {
184 let usage_res = self
185 .usage_publisher
186 .request(format!("WatchLightGroup{{name:{name:?}}}"), RequestType::Get);
187 let res = if let Some(subscriber) = self.group_subscribers.get(&name) {
188 subscriber.register2((usage_res, responder)).map_err(
189 |(usage_res, responder)| {
190 (HandlerError::AlreadySubscribed, usage_res, responder)
191 },
192 )
193 } else {
194 Err((HandlerError::NotFound, usage_res, responder))
195 };
196 if let Err((e, usage_res, responder)) = res {
197 usage_res.respond(format!("Err({e:?})"), ResponseType::from(&e));
198 drop(responder);
199 }
200 }
201 LightRequest::SetLightGroupValues { name, state, responder } => {
202 let usage_res = self.usage_publisher.request(
203 format!("SetLightGroupValues{{name:{name:?},state:{state:?}}}"),
204 RequestType::Set,
205 );
206 if let Err(e) = self.set(name, state).await {
207 usage_res.respond(format!("Err({e:?}"), ResponseType::from(&e));
208 let _ = responder.send(Err(LightError::from(e)));
209 } else {
210 usage_res.respond("Ok(())".to_string(), ResponseType::OkNone);
211 let _ = responder.send(Ok(()));
212 }
213 }
214 }
215 }
216
217 async fn set(&self, name: String, state: Vec<LightState>) -> Result<(), HandlerError> {
218 let (set_tx, set_rx) = oneshot::channel();
219 self.controller_tx
220 .unbounded_send(Request::SetLightGroupValue(
221 name,
222 state.into_iter().map(LightState::into).collect::<Vec<_>>(),
223 set_tx,
224 ))
225 .map_err(|_| HandlerError::ControllerStopped)?;
226 set_rx
227 .await
228 .map_err(|_| HandlerError::ControllerStopped)
229 .and_then(|res| res.map_err(HandlerError::Controller))
230 }
231}
232
233impl From<&LightInfo> for Vec<FidlLightGroup> {
234 fn from(info: &LightInfo) -> Self {
235 info.light_groups.values().cloned().map(FidlLightGroup::from).collect::<Vec<_>>()
237 }
238}
239
240#[cfg(test)]
241mod tests {
242 use super::*;
243 use crate::types::{LightState, LightType, LightValue};
244
245 #[fuchsia::test]
246 fn test_response_to_vector_empty() {
247 let response: Vec<fidl_fuchsia_settings::LightGroup> =
248 (&LightInfo { light_groups: Default::default() }).into();
249
250 assert_eq!(response, vec![]);
251 }
252
253 #[fuchsia::test]
254 fn test_response_to_vector() {
255 let light_group_1 = LightGroup {
256 name: "test".to_string(),
257 enabled: true,
258 light_type: LightType::Simple,
259 lights: vec![LightState { value: Some(LightValue::Simple(true)) }],
260 hardware_index: vec![],
261 disable_conditions: vec![],
262 };
263 let light_group_2 = LightGroup {
264 name: "test2".to_string(),
265 enabled: false,
266 light_type: LightType::Rgb,
267 lights: vec![LightState { value: Some(LightValue::Brightness(0.42)) }],
268 hardware_index: vec![],
269 disable_conditions: vec![],
270 };
271
272 let light_groups: HashMap<_, _> = IntoIterator::into_iter([
273 (String::from("test"), light_group_1.clone()),
274 (String::from("test2"), light_group_2.clone()),
275 ])
276 .collect();
277
278 let mut response: Vec<fidl_fuchsia_settings::LightGroup> =
279 (&LightInfo { light_groups }).into();
280
281 response.sort_by_key(|l| l.name.clone());
283
284 assert_eq!(
285 response,
286 vec![FidlLightGroup::from(light_group_1), FidlLightGroup::from(light_group_2)]
287 );
288 }
289}