diff --git a/garnet/bin/setui/src/common.rs b/garnet/bin/setui/src/common.rs new file mode 100644 index 0000000000000000000000000000000000000000..e6c60f9967dbb71763e2fb4c6d19fdfa7df8aad1 --- /dev/null +++ b/garnet/bin/setui/src/common.rs @@ -0,0 +1,25 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use failure::Error; +use fidl_fuchsia_setui::*; +use std::sync::mpsc::Sender; + +pub type ProcessMutation = dyn Fn(&Mutation) -> Result<Option<SettingData>, Error> + Send + Sync; + +/// Trait defining interface setting service uses to relay operations. Each +/// adapter specifies the setting type it handles, which it will accept +/// mutations for and relay updates. +pub trait Adapter { + /// Returns the setting type this adapter is responsible for handling. + fn get_type(&self) -> SettingType; + + /// Applies a mutation on the given adapter. + fn mutate(&mut self, mutation: &fidl_fuchsia_setui::Mutation) -> MutationResponse; + + /// Registers a listener. The current value known to the client is passed + /// along. If an updated value is known, the sender is immediately invoked. + /// Otherwise, the sender is stored for later invocation. + fn listen(&self, sender: Sender<SettingData>, last_seen_data: Option<&SettingData>); +} diff --git a/garnet/bin/setui/src/fidl_clone.rs b/garnet/bin/setui/src/fidl_clone.rs new file mode 100644 index 0000000000000000000000000000000000000000..4d80df338d3b5c53bce255a6f83bdcba5395a67c --- /dev/null +++ b/garnet/bin/setui/src/fidl_clone.rs @@ -0,0 +1,118 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use fidl_fuchsia_setui::*; + +/// A placeholder for real cloning support in FIDL generated Rust code. +/// TODO(QA-715): Remove +pub trait FIDLClone { + fn clone(&self) -> Self; +} + +impl FIDLClone for SettingData { + fn clone(&self) -> SettingData { + match self { + SettingData::StringValue(val) => { + return SettingData::StringValue(val.to_string()); + } + SettingData::TimeZoneValue(val) => { + return SettingData::TimeZoneValue(val.clone()); + } + SettingData::Connectivity(val) => { + return SettingData::Connectivity(val.clone()); + } + SettingData::Intl(val) => { + return SettingData::Intl(val.clone()); + } + SettingData::Wireless(val) => { + return SettingData::Wireless(val.clone()); + } + SettingData::Account(val) => { + return SettingData::Account(val.clone()); + } + } + } +} + +impl FIDLClone for AccountSettings { + fn clone(&self) -> Self { + return AccountSettings { mode: self.mode }; + } +} + +impl FIDLClone for IntlSettings { + fn clone(&self) -> Self { + return IntlSettings { + locales: self.locales.clone(), + hour_cycle: self.hour_cycle, + temperature_unit: self.temperature_unit, + }; + } +} + +impl FIDLClone for ConnectedState { + fn clone(&self) -> ConnectedState { + return ConnectedState { reachability: self.reachability }; + } +} + +impl<T: FIDLClone> FIDLClone for Vec<T> { + fn clone(&self) -> Self { + return self.into_iter().map(FIDLClone::clone).collect(); + } +} + +impl<T: FIDLClone> FIDLClone for Option<Box<T>> { + fn clone(&self) -> Self { + match self { + None => None, + Some(val) => Some(Box::new(val.as_ref().clone())), + } + } +} + +impl FIDLClone for TimeZoneInfo { + fn clone(&self) -> TimeZoneInfo { + return TimeZoneInfo { current: self.current.clone(), available: self.available.clone() }; + } +} + +impl FIDLClone for TimeZone { + fn clone(&self) -> Self { + return TimeZone { + id: self.id.clone(), + name: self.name.clone(), + region: self.region.clone(), + }; + } +} + +impl FIDLClone for WirelessState { + fn clone(&self) -> Self { + return WirelessState { wireless_networks: self.wireless_networks.clone() }; + } +} + +impl FIDLClone for WirelessNetwork { + fn clone(&self) -> Self { + return WirelessNetwork { + internal_id: self.internal_id, + ssid: self.ssid.clone(), + wpa_auth: self.wpa_auth, + wpa_cipher: self.wpa_cipher, + access_points: self.access_points.clone(), + }; + } +} + +impl FIDLClone for WirelessAccessPoint { + fn clone(&self) -> Self { + return WirelessAccessPoint { + bssid: self.bssid.clone(), + frequency: self.frequency, + rssi: self.rssi, + status: self.status, + }; + } +} diff --git a/garnet/bin/setui/src/main.rs b/garnet/bin/setui/src/main.rs index fbed5784a129ba24f9469be4ab57b39af03eab9f..ce0163714fe672a56c595c5b6f0ae6568001efae 100644 --- a/garnet/bin/setui/src/main.rs +++ b/garnet/bin/setui/src/main.rs @@ -4,6 +4,8 @@ #![feature(async_await, await_macro, futures_api)] use { + crate::mutation::*, + crate::setting_adapter::SettingAdapter, failure::Error, fidl_fuchsia_setui::*, fuchsia_async as fasync, @@ -15,6 +17,10 @@ use { std::sync::Arc, }; +mod common; +mod fidl_clone; +mod mutation; +mod setting_adapter; mod setui_handler; fn main() -> Result<(), Error> { @@ -26,6 +32,13 @@ fn main() -> Result<(), Error> { let mut fs = ServiceFs::new(); let handler = Arc::new(SetUIHandler::new()); + // TODO(SU-210): Remove once other adapters are ready. + handler.register_adapter(Box::new(SettingAdapter::new( + SettingType::Unknown, + Box::new(process_string_mutation), + None, + ))); + fs.dir("public").add_fidl_service(move |stream: SetUiServiceRequestStream| { let handler_clone = handler.clone(); fx_log_info!("Connecting to setui_service"); diff --git a/garnet/bin/setui/src/mutation.rs b/garnet/bin/setui/src/mutation.rs new file mode 100644 index 0000000000000000000000000000000000000000..0eee85710e2e7b37525ffadc4781b745058c47b6 --- /dev/null +++ b/garnet/bin/setui/src/mutation.rs @@ -0,0 +1,17 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. +use failure::{format_err, Error}; +use fidl_fuchsia_setui::*; + +pub fn process_string_mutation(mutation: &Mutation) -> Result<Option<SettingData>, Error> { + if let Mutation::StringMutationValue(mutation_info) = mutation { + if mutation_info.operation == StringOperation::Update { + return Ok(Some(SettingData::StringValue(mutation_info.value.clone()))); + } + + return Ok(None); + } else { + return Err(format_err!("invalid error")); + } +} diff --git a/garnet/bin/setui/src/setting_adapter.rs b/garnet/bin/setui/src/setting_adapter.rs new file mode 100644 index 0000000000000000000000000000000000000000..a039d522215144cc3ab361d39b89bff1d0691243 --- /dev/null +++ b/garnet/bin/setui/src/setting_adapter.rs @@ -0,0 +1,80 @@ +// Copyright 2019 The Fuchsia Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +use crate::common::*; +use crate::fidl_clone::*; +use fidl_fuchsia_setui::*; +use std::sync::mpsc::Sender; +use std::sync::Mutex; + +/// SettingAdapter provides a basic implementation of the Adapter trait, +/// handling callbacks for hanging get interactions and keeping track of the +/// latest values. Users of this implementation can provide a callback for +/// processing mutations. +pub struct SettingAdapter { + setting_type: SettingType, + latest_val: Option<SettingData>, + senders: Mutex<Vec<Sender<SettingData>>>, + mutation_process: Box<ProcessMutation>, +} + +impl SettingAdapter { + pub fn new( + setting_type: SettingType, + mutation_processor: Box<ProcessMutation>, + default_value: Option<SettingData>, + ) -> SettingAdapter { + return SettingAdapter { + setting_type: setting_type, + latest_val: default_value, + senders: Mutex::new(vec![]), + mutation_process: mutation_processor, + }; + } +} + +impl Adapter for SettingAdapter { + fn get_type(&self) -> SettingType { + return self.setting_type; + } + + fn mutate(&mut self, mutation: &fidl_fuchsia_setui::Mutation) -> MutationResponse { + let result = (self.mutation_process)(mutation); + + match result { + Ok(Some(setting)) => { + self.latest_val = Some(setting.clone()); + + if let Ok(mut senders) = self.senders.lock() { + while !senders.is_empty() { + let sender_option = senders.pop(); + + if let Some(sender) = sender_option { + sender.send(setting.clone()).ok(); + } + } + } + } + Ok(None) => { + self.latest_val = None; + } + _ => { + return MutationResponse { return_code: ReturnCode::Failed }; + } + } + + return MutationResponse { return_code: ReturnCode::Ok }; + } + + /// Listen + fn listen(&self, sender: Sender<SettingData>, _last_seen_data: Option<&SettingData>) { + if let Some(ref value) = self.latest_val { + sender.send(value.clone()).ok(); + } else { + if let Ok(mut senders) = self.senders.lock() { + senders.push(sender); + } + } + } +} diff --git a/garnet/bin/setui/src/setui_handler.rs b/garnet/bin/setui/src/setui_handler.rs index 179b556747d3e3c17c41d144cadefd1549f3db44..506f276eb5f506d82b88ee7d3b8db02392d9c83f 100644 --- a/garnet/bin/setui/src/setui_handler.rs +++ b/garnet/bin/setui/src/setui_handler.rs @@ -2,44 +2,112 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -use failure::Error; +use crate::common::Adapter; +use crate::fidl_clone::*; +use failure::{format_err, Error}; use fidl_fuchsia_setui::*; +use fuchsia_syslog::fx_log_err; use futures::prelude::*; +use std::collections::HashMap; +use std::sync::mpsc::{channel, Sender}; +use std::sync::Arc; +use std::sync::Mutex; +use std::sync::RwLock; -pub struct SetUIHandler {} +use fuchsia_async as fasync; + +pub struct SetUIHandler { + // Tracks active adapters. + adapters: RwLock<HashMap<SettingType, Mutex<Box<dyn Adapter + Send + Sync>>>>, + // Provides bookkeeping for last value transmitted by a callback. Asynchronous + // to allow sharing between all callback closures. + last_seen_settings: Arc<RwLock<HashMap<SettingType, SettingData>>>, +} /// SetUIHandler handles all API calls for the service. It is intended to be /// used as a single instance to service multiple streams. impl SetUIHandler { - /// In the future, will populate with supporting classes, such as adapters. pub fn new() -> SetUIHandler { - Self {} + Self { + adapters: RwLock::new(HashMap::new()), + last_seen_settings: Arc::new(RwLock::new(HashMap::new())), + } + } + + /// Adds (or replaces) any existing adapter. Setting type mapping is + /// determined from the adapter's reported type. + pub fn register_adapter(&self, adapter: Box<dyn Adapter + Send + Sync>) { + self.adapters.write().unwrap().insert(adapter.get_type(), Mutex::new(adapter)); } /// Asynchronous handling of the given stream. Note that we must consider the /// possibility of simultaneous active streams. pub async fn handle_stream(&self, mut stream: SetUiServiceRequestStream) -> Result<(), Error> { + // A single StreamSetting watcher is created for the lifetime of the stream. + // It is used for asynchronously processing updates to a setting type and + // tracking the current values. + while let Some(req) = await!(stream.try_next())? { - await!(self.handle_request(req))?; + match req { + SetUiServiceRequest::Mutate { setting_type, mutation, responder } => { + let mut response = self.mutate(setting_type, mutation); + responder.send(&mut response)?; + } + SetUiServiceRequest::Watch { setting_type, responder } => { + self.watch(setting_type, responder); + } + _ => {} + } } Ok(()) } - /// Routes a given request to the proper handling function. - pub async fn handle_request(&self, req: SetUiServiceRequest) -> Result<(), fidl::Error> { - match req { - SetUiServiceRequest::Mutate { setting_type, mutation, responder } => { - let mut response = self.mutate(setting_type, mutation); - responder.send(&mut response)?; + fn watch(&self, setting_type: SettingType, responder: SetUiServiceWatchResponder) { + let (sender, receiver) = channel(); + + if self.listen(setting_type, sender).is_ok() { + // We clone here so the value can be moved into the closure below. + let last_seen_settings_clone = self.last_seen_settings.clone(); + + fasync::spawn( + async move { + let data: SettingData = receiver.recv().unwrap(); + last_seen_settings_clone.write().unwrap().insert(setting_type, data.clone()); + responder + .send(&mut SettingsObject { + setting_type: setting_type, + data: data.clone(), + }) + .ok(); + }, + ); + } else { + fx_log_err!("watch: no valid adapter for type"); + } + } + + /// The core logic behind listening to a setting is separate from the watch + /// method definition to facilitate testing. + fn listen(&self, setting_type: SettingType, sender: Sender<SettingData>) -> Result<(), Error> { + if let Some(adapter_lock) = self.adapters.read().unwrap().get(&setting_type) { + if let Ok(adapter) = adapter_lock.lock() { + adapter.listen(sender, self.last_seen_settings.read().unwrap().get(&setting_type)); + return Ok(()); } - _ => {} } - Ok(()) + + return Err(format_err!("cannot listen. non-existent adapter")); } /// Applies a mutation - fn mutate(&self, _setting_type: SettingType, _mutation: Mutation) -> MutationResponse { + fn mutate(&self, setting_type: SettingType, mutation: Mutation) -> MutationResponse { + if let Some(adapter_lock) = self.adapters.read().unwrap().get(&setting_type) { + if let Ok(mut adapter) = adapter_lock.lock() { + adapter.mutate(&mutation); + } + } + MutationResponse { return_code: ReturnCode::Ok } } } @@ -47,6 +115,8 @@ impl SetUIHandler { #[cfg(test)] mod tests { use super::*; + use crate::mutation::*; + use crate::setting_adapter::SettingAdapter; /// A basic test to exercise that basic functionality works. In this case, we /// mutate the unknown type, reserved for testing. We should always immediately @@ -65,4 +135,51 @@ mod tests { assert_eq!(result, MutationResponse { return_code: ReturnCode::Ok }); } + /// A test to verify the listen functionality. Note that we use the listen + /// method directly on the handler as we do not mock the FIDL interface. + #[test] + fn test_listen() { + // Test value to ensure + let test_val = "FooBar".to_string(); + // Create handler and register test adapter + let handler = SetUIHandler::new(); + handler.register_adapter(Box::new(SettingAdapter::new( + SettingType::Unknown, + Box::new(process_string_mutation), + None, + ))); + + // Introduce change to the adapter. + assert_eq!( + handler.mutate( + SettingType::Unknown, + fidl_fuchsia_setui::Mutation::StringMutationValue(StringMutation { + operation: StringOperation::Update, + value: test_val.clone() + }), + ), + MutationResponse { return_code: ReturnCode::Ok } + ); + + let (sender, receiver) = channel(); + + // Listen for change + assert!(handler.listen(SettingType::Unknown, sender).is_ok()); + + let listen_result = receiver.recv(); + + assert!(listen_result.is_ok()); + + let data = listen_result.unwrap(); + + // Ensure value matches original change. + match data { + SettingData::StringValue(string_val) => { + assert_eq!(string_val, test_val.clone()); + } + _ => { + panic!("Did not receive back expected SettingData type"); + } + } + } }