settings/intl/
intl_controller.rsuse crate::base::{Merge, SettingInfo, SettingType};
use crate::handler::base::Request;
use crate::handler::setting_handler::persist::{controller as data_controller, ClientProxy};
use crate::handler::setting_handler::{
controller, ControllerError, IntoHandlerResult, SettingHandlerResult,
};
use crate::intl::types::{HourCycle, IntlInfo, LocaleId, TemperatureUnit};
use async_trait::async_trait;
use settings_storage::device_storage::{DeviceStorage, DeviceStorageCompatible};
use settings_storage::fidl_storage::FidlStorageConvertible;
use settings_storage::storage_factory::{NoneT, StorageAccess};
use std::collections::HashSet;
use {fuchsia_trace as ftrace, rust_icu_uenum as uenum, rust_icu_uloc as uloc};
impl DeviceStorageCompatible for IntlInfo {
type Loader = NoneT;
const KEY: &'static str = "intl_info";
}
impl FidlStorageConvertible for IntlInfo {
type Storable = fidl_fuchsia_settings::IntlSettings;
type Loader = NoneT;
const KEY: &'static str = "intl";
fn to_storable(self) -> Self::Storable {
self.into()
}
fn from_storable(storable: Self::Storable) -> Self {
storable.into()
}
}
impl Default for IntlInfo {
fn default() -> Self {
IntlInfo {
locales: Some(vec![LocaleId { id: "en-US-x-fxdef".to_string() }]),
temperature_unit: Some(TemperatureUnit::Celsius),
time_zone_id: Some("UTC".to_string()),
hour_cycle: Some(HourCycle::H12),
}
}
}
impl From<IntlInfo> for SettingInfo {
fn from(info: IntlInfo) -> SettingInfo {
SettingInfo::Intl(info)
}
}
pub struct IntlController {
client: ClientProxy,
time_zone_ids: std::collections::HashSet<String>,
}
impl StorageAccess for IntlController {
type Storage = DeviceStorage;
type Data = IntlInfo;
const STORAGE_KEY: &'static str = <IntlInfo as DeviceStorageCompatible>::KEY;
}
#[async_trait(?Send)]
impl data_controller::Create for IntlController {
async fn create(client: ClientProxy) -> Result<Self, ControllerError> {
let time_zone_ids = IntlController::load_time_zones();
Ok(IntlController { client, time_zone_ids })
}
}
#[async_trait(?Send)]
impl controller::Handle for IntlController {
async fn handle(&self, request: Request) -> Option<SettingHandlerResult> {
match request {
Request::SetIntlInfo(info) => Some(self.set(info).await),
Request::Get => Some(
self.client
.read_setting_info::<IntlInfo>(ftrace::Id::new())
.await
.into_handler_result(),
),
_ => None,
}
}
}
impl IntlController {
fn load_time_zones() -> std::collections::HashSet<String> {
let _icu_data_loader = icu_data::Loader::new().expect("icu data loaded");
let time_zone_list = match uenum::open_time_zones() {
Ok(time_zones) => time_zones,
Err(err) => {
tracing::error!("Unable to load time zones: {:?}", err);
return HashSet::new();
}
};
time_zone_list.flatten().collect()
}
async fn set(&self, info: IntlInfo) -> SettingHandlerResult {
self.validate_intl_info(info.clone())?;
let id = ftrace::Id::new();
let current = self.client.read_setting::<IntlInfo>(id).await;
self.client.write_setting(current.merge(info).into(), id).await.into_handler_result()
}
#[allow(clippy::result_large_err)] fn validate_intl_info(&self, info: IntlInfo) -> Result<(), ControllerError> {
if let Some(time_zone_id) = info.time_zone_id {
if !self.time_zone_ids.contains(time_zone_id.as_str()) {
return Err(ControllerError::InvalidArgument(
SettingType::Intl,
"timezone id".into(),
time_zone_id.into(),
));
}
}
if let Some(time_zone_locale) = info.locales {
for locale in time_zone_locale {
let loc = uloc::ULoc::for_language_tag(locale.id.as_str());
match loc {
Ok(parsed) => {
if parsed.label().is_empty() {
tracing::error!("Locale is invalid: {:?}", locale.id);
return Err(ControllerError::InvalidArgument(
SettingType::Intl,
"locale id".into(),
locale.id.into(),
));
}
}
Err(err) => {
tracing::error!("Error loading locale: {:?}", err);
return Err(ControllerError::InvalidArgument(
SettingType::Intl,
"locale id".into(),
locale.id.into(),
));
}
}
}
}
Ok(())
}
}