settings_light/
light_fidl_handler.rs

1// Copyright 2020 The Fuchsia Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5use 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        // Internally we store the data in a HashMap, need to flatten it out into a vector.
236        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        // Sort so light groups are in a predictable order.
282        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}