use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use fidl::prelude::*;
use fidl_fuchsia_settings::{
LightError, LightGroup, LightRequest, LightSetLightGroupValuesResponder,
LightSetLightGroupValuesResult, LightState, LightWatchLightGroupResponder,
LightWatchLightGroupsResponder,
};
use zx::Status;
use crate::base::{SettingInfo, SettingType};
use crate::handler;
use crate::handler::base::{Request, Response};
use crate::ingress::{request, watch, Scoped};
use crate::job::source::Error as JobError;
use crate::job::Job;
use crate::light::light_controller::ARG_NAME;
impl watch::Responder<Vec<LightGroup>, zx::Status> for LightWatchLightGroupsResponder {
fn respond(self, response: Result<Vec<LightGroup>, zx::Status>) {
match response {
Ok(light_groups) => {
let _ = self.send(&light_groups);
}
Err(error) => {
self.control_handle().shutdown_with_epitaph(error);
}
}
}
}
impl watch::Responder<Vec<LightGroup>, zx::Status> for IndividualLightGroupResponder {
fn respond(self, response: Result<Vec<LightGroup>, zx::Status>) {
let light_group_name = self.light_group_name;
match response {
Ok(light_groups) => {
match light_groups.into_iter().find(|group| {
group.name.as_ref().map(|n| *n == light_group_name).unwrap_or(false)
}) {
Some(group) => {
let _ = self.responder.send(&group);
}
None => {
self.responder.control_handle().shutdown_with_epitaph(Status::NOT_FOUND);
}
}
}
Err(error) => {
self.responder.control_handle().shutdown_with_epitaph(error);
}
}
}
}
impl From<SettingInfo> for Vec<LightGroup> {
fn from(info: SettingInfo) -> Self {
if let SettingInfo::Light(light_info) = info {
light_info.light_groups.values().cloned().map(LightGroup::from).collect::<Vec<_>>()
} else {
panic!("incorrect value sent to light: {info:?}");
}
}
}
impl From<Response> for Scoped<LightSetLightGroupValuesResult> {
fn from(response: Response) -> Self {
Scoped(response.map(|_| ()).map_err(|e| match e {
handler::base::Error::InvalidArgument(_, argument, _) => {
if ARG_NAME == argument {
LightError::InvalidName
} else {
LightError::InvalidValue
}
}
_ => LightError::Failed,
}))
}
}
impl request::Responder<Scoped<LightSetLightGroupValuesResult>>
for LightSetLightGroupValuesResponder
{
fn respond(self, Scoped(response): Scoped<LightSetLightGroupValuesResult>) {
let _ = self.send(response);
}
}
impl TryFrom<LightRequest> for Job {
type Error = JobError;
fn try_from(item: LightRequest) -> Result<Self, Self::Error> {
#[allow(unreachable_patterns)]
match item {
LightRequest::WatchLightGroups { responder } => {
Ok(watch::Work::new_job(SettingType::Light, responder))
}
LightRequest::WatchLightGroup { name, responder } => {
let mut hasher = DefaultHasher::new();
name.hash(&mut hasher);
let name_clone = name.clone();
Ok(watch::Work::new_job_with_change_function(
SettingType::Light,
IndividualLightGroupResponder { responder, light_group_name: name },
watch::ChangeFunction::new(
hasher.finish(),
Box::new(move |old: &SettingInfo, new: &SettingInfo| match (old, new) {
(SettingInfo::Light(old_info), SettingInfo::Light(new_info)) => {
let old_light_group = old_info.light_groups.get(&name_clone);
let new_light_group = new_info.light_groups.get(&name_clone);
old_light_group != new_light_group
}
_ => false,
}),
),
))
}
LightRequest::SetLightGroupValues { name, state, responder } => Ok(request::Work::new(
SettingType::Light,
Request::SetLightGroupValue(
name,
state.into_iter().map(LightState::into).collect::<Vec<_>>(),
),
responder,
)
.into()),
_ => {
tracing::warn!("Received a call to an unsupported API: {:?}", item);
Err(JobError::Unsupported)
}
}
}
}
struct IndividualLightGroupResponder {
responder: LightWatchLightGroupResponder,
light_group_name: String,
}
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use fidl_fuchsia_settings::{LightMarker, LightRequestStream};
use futures::StreamExt;
use assert_matches::assert_matches;
use crate::job::{execution, work, Signature};
use crate::light::types::{LightGroup, LightInfo, LightState, LightType, LightValue};
use super::*;
#[fuchsia::test]
fn test_response_to_vector_empty() {
let response: Vec<fidl_fuchsia_settings::LightGroup> =
SettingInfo::into((LightInfo { light_groups: Default::default() }).into());
assert_eq!(response, vec![]);
}
#[fuchsia::test]
fn test_response_to_vector() {
let light_group_1 = LightGroup {
name: "test".to_string(),
enabled: true,
light_type: LightType::Simple,
lights: vec![LightState { value: Some(LightValue::Simple(true)) }],
hardware_index: vec![],
disable_conditions: vec![],
};
let light_group_2 = LightGroup {
name: "test2".to_string(),
enabled: false,
light_type: LightType::Rgb,
lights: vec![LightState { value: Some(LightValue::Brightness(0.42)) }],
hardware_index: vec![],
disable_conditions: vec![],
};
let light_groups: HashMap<_, _> = IntoIterator::into_iter([
(String::from("test"), light_group_1.clone()),
(String::from("test2"), light_group_2.clone()),
])
.collect();
let mut response: Vec<fidl_fuchsia_settings::LightGroup> =
SettingInfo::into((LightInfo { light_groups }).into());
response.sort_by_key(|l| l.name.clone());
assert_eq!(response, vec![light_group_1.into(), light_group_2.into()]);
}
#[fuchsia::test(allow_stalls = false)]
async fn try_from_watch_light_groups_request() {
let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
let _fut = proxy.watch_light_groups();
let mut request_stream: LightRequestStream = server.into_stream();
let request = request_stream
.next()
.await
.expect("should have an request before stream is closed")
.expect("should have gotten a request");
let job = Job::try_from(request).expect("job conversion should succeed");
assert_matches!(job.workload(), work::Load::Sequential(_, _));
assert_matches!(job.execution_type(), execution::Type::Sequential(_));
}
async fn signature_from_watch_light_group_request(light_name: &str) -> Signature {
let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
let _fut = proxy.watch_light_group(light_name);
let mut request_stream: LightRequestStream = server.into_stream();
let request = request_stream
.next()
.await
.expect("should have an request before stream is closed")
.expect("should have gotten a request");
let job = Job::try_from(request).expect("job conversion should succeed");
assert_matches!(job.workload(), work::Load::Sequential(_, _));
assert_matches!(job.execution_type(), execution::Type::Sequential(signature) => signature)
}
#[fuchsia::test(allow_stalls = false)]
async fn try_from_watch_individual_light_group_request() {
const TEST_LIGHT_NAME: &str = "test_light";
let signature = signature_from_watch_light_group_request(TEST_LIGHT_NAME).await;
let signature2 = signature_from_watch_light_group_request(TEST_LIGHT_NAME).await;
assert_eq!(signature, signature2);
let signature3 = signature_from_watch_light_group_request("different_name").await;
assert_ne!(signature, signature3);
}
#[fuchsia::test(allow_stalls = false)]
async fn try_from_set_light_group_values_request() {
let (proxy, server) = fidl::endpoints::create_proxy::<LightMarker>();
let _fut = proxy.set_light_group_values("arbitrary name", &[]);
let mut request_stream: LightRequestStream = server.into_stream();
let request = request_stream
.next()
.await
.expect("should have an request before stream is closed")
.expect("should have gotten a request");
let job = Job::try_from(request).expect("job conversion should succeed");
assert_matches!(job.workload(), work::Load::Independent(_));
assert_matches!(job.execution_type(), execution::Type::Independent);
}
}