Skip to main content

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::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        // Internally we store the data in a HashMap, need to flatten it out into a vector.
234        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        // Sort so light groups are in a predictable order.
280        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}