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::StreamExt;
14use futures::channel::mpsc::{self, UnboundedReceiver, UnboundedSender};
15use futures::channel::oneshot;
16
17use crate::light_controller::{
18 ARG_NAME, LightController, LightError as ControllerLightError, Request,
19};
20use crate::types::{LightGroup, LightInfo};
21use settings_common::inspect::event::{
22 HangingGetObserver, RequestType, ResponseType, UsagePublisher,
23};
24
25pub(crate) type SubscriberObject<T> = HangingGetObserver<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, observer| {
83 let (usage_responder, responder) = observer.into_parts();
84 usage_responder.respond(format!("{group:?}"), ResponseType::OkSome);
85 if let Err(e) = responder.send(&FidlLightGroup::from(group.clone())) {
86 log::warn!("Failed to respond to watch light group request: {e:?}");
87 return false;
88 }
89 true
90 }),
91 ),
92 )
93 })
94 .collect();
95 let info_hanging_get: InfoHangingGet = InfoHangingGet::new(
96 info,
97 Box::new(|info, observer| {
98 let (usage_responder, responder) = observer.into_parts();
99 usage_responder.respond(format!("{info:?}"), ResponseType::OkSome);
100 if let Err(e) = responder.send(&Vec::<FidlLightGroup>::from(info)) {
101 log::warn!("Failed to respond to watch light groups request: {e:?}");
102 return false;
103 }
104 true
105 }),
106 );
107 (info_hanging_get, group_hanging_gets)
108 }
109
110 pub fn handle_stream(&mut self, mut stream: LightRequestStream) {
111 let request_handler = RequestHandler {
112 info_subscriber: self.info_hanging_get.new_subscriber(),
113 group_subscribers: self
114 .group_hanging_gets
115 .iter_mut()
116 .map(|(key, hanging_get)| (key.clone(), hanging_get.new_subscriber()))
117 .collect::<HashMap<_, _>>(),
118 controller_tx: self.controller_tx.clone(),
119 usage_publisher: self.usage_publisher.clone(),
120 };
121 fasync::Task::local(async move {
122 while let Some(Ok(request)) = stream.next().await {
123 request_handler.handle_request(request).await;
124 }
125 })
126 .detach();
127 }
128}
129
130#[derive(Debug)]
131enum HandlerError {
132 AlreadySubscribed,
133 NotFound,
134 ControllerStopped,
135 Controller(ControllerLightError),
136}
137
138impl From<&HandlerError> for ResponseType {
139 fn from(error: &HandlerError) -> Self {
140 match error {
141 HandlerError::AlreadySubscribed => ResponseType::AlreadySubscribed,
142 HandlerError::NotFound => ResponseType::InvalidArgument,
143 HandlerError::ControllerStopped => ResponseType::UnexpectedError,
144 HandlerError::Controller(e) => ResponseType::from(e),
145 }
146 }
147}
148
149impl From<HandlerError> for LightError {
150 fn from(error: HandlerError) -> Self {
151 if let HandlerError::Controller(ControllerLightError::InvalidArgument(argument, _)) = error
152 {
153 if ARG_NAME == argument { LightError::InvalidName } else { LightError::InvalidValue }
154 } else {
155 LightError::Failed
156 }
157 }
158}
159
160struct RequestHandler {
161 info_subscriber: InfoSubscriber,
162 group_subscribers: HashMap<String, GroupSubscriber>,
163 controller_tx: UnboundedSender<Request>,
164 usage_publisher: UsagePublisher<LightInfo>,
165}
166
167impl RequestHandler {
168 async fn handle_request(&self, request: LightRequest) {
169 match request {
170 LightRequest::WatchLightGroups { responder } => {
171 let usage_res =
172 self.usage_publisher.request("WatchLightGroups".to_string(), RequestType::Get);
173 let observer = HangingGetObserver::new(usage_res, responder);
174 if let Err(observer) = self.info_subscriber.register2(observer) {
175 let e = HandlerError::AlreadySubscribed;
176 let (usage_res, responder) = observer.into_parts();
177 usage_res.respond(format!("Err({e:?})"), ResponseType::from(&e));
178 drop(responder);
179 }
180 }
181 LightRequest::WatchLightGroup { name, responder } => {
182 let usage_res = self
183 .usage_publisher
184 .request(format!("WatchLightGroup{{name:{name:?}}}"), RequestType::Get);
185 let res = if let Some(subscriber) = self.group_subscribers.get(&name) {
186 let observer = HangingGetObserver::new(usage_res, responder);
187 subscriber.register2(observer).map_err(|observer| {
188 let (usage_res, responder) = observer.into_parts();
189 (HandlerError::AlreadySubscribed, usage_res, responder)
190 })
191 } else {
192 Err((HandlerError::NotFound, usage_res, responder))
193 };
194 if let Err((e, usage_res, responder)) = res {
195 usage_res.respond(format!("Err({e:?})"), ResponseType::from(&e));
196 drop(responder);
197 }
198 }
199 LightRequest::SetLightGroupValues { name, state, responder } => {
200 let usage_res = self.usage_publisher.request(
201 format!("SetLightGroupValues{{name:{name:?},state:{state:?}}}"),
202 RequestType::Set,
203 );
204 if let Err(e) = self.set(name, state).await {
205 usage_res.respond(format!("Err({e:?}"), ResponseType::from(&e));
206 let _ = responder.send(Err(LightError::from(e)));
207 } else {
208 usage_res.respond("Ok(())".to_string(), ResponseType::OkNone);
209 let _ = responder.send(Ok(()));
210 }
211 }
212 }
213 }
214
215 async fn set(&self, name: String, state: Vec<LightState>) -> Result<(), HandlerError> {
216 let (set_tx, set_rx) = oneshot::channel();
217 self.controller_tx
218 .unbounded_send(Request::SetLightGroupValue(
219 name,
220 state.into_iter().map(LightState::into).collect::<Vec<_>>(),
221 set_tx,
222 ))
223 .map_err(|_| HandlerError::ControllerStopped)?;
224 set_rx
225 .await
226 .map_err(|_| HandlerError::ControllerStopped)
227 .and_then(|res| res.map_err(HandlerError::Controller))
228 }
229}
230
231impl From<&LightInfo> for Vec<FidlLightGroup> {
232 fn from(info: &LightInfo) -> Self {
233 info.light_groups.values().cloned().map(FidlLightGroup::from).collect::<Vec<_>>()
235 }
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241 use crate::types::{LightState, LightType, LightValue};
242
243 #[fuchsia::test]
244 fn test_response_to_vector_empty() {
245 let response: Vec<fidl_fuchsia_settings::LightGroup> =
246 (&LightInfo { light_groups: Default::default() }).into();
247
248 assert_eq!(response, vec![]);
249 }
250
251 #[fuchsia::test]
252 fn test_response_to_vector() {
253 let light_group_1 = LightGroup {
254 name: "test".to_string(),
255 enabled: true,
256 light_type: LightType::Simple,
257 lights: vec![LightState { value: Some(LightValue::Simple(true)) }],
258 hardware_index: vec![],
259 disable_conditions: vec![],
260 };
261 let light_group_2 = LightGroup {
262 name: "test2".to_string(),
263 enabled: false,
264 light_type: LightType::Rgb,
265 lights: vec![LightState { value: Some(LightValue::Brightness(0.42)) }],
266 hardware_index: vec![],
267 disable_conditions: vec![],
268 };
269
270 let light_groups: HashMap<_, _> = IntoIterator::into_iter([
271 (String::from("test"), light_group_1.clone()),
272 (String::from("test2"), light_group_2.clone()),
273 ])
274 .collect();
275
276 let mut response: Vec<fidl_fuchsia_settings::LightGroup> =
277 (&LightInfo { light_groups }).into();
278
279 response.sort_by_key(|l| l.name.clone());
281
282 assert_eq!(
283 response,
284 vec![FidlLightGroup::from(light_group_1), FidlLightGroup::from(light_group_2)]
285 );
286 }
287}