Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adapt product selection and handling ofl10n settings with the HTTP/JSON server #1094

Merged
merged 13 commits into from
Mar 15, 2024
Merged
5 changes: 3 additions & 2 deletions rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions rust/agama-lib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jsonschema = { version = "0.16.1", default-features = false }
log = "0.4"
serde = { version = "1.0.152", features = ["derive"] }
serde_json = "1.0.94"
serde_repr = "0.1.18"
tempfile = "3.4.0"
thiserror = "1.0.39"
tokio = { version = "1.33.0", features = ["macros", "rt-multi-thread"] }
Expand Down
6 changes: 4 additions & 2 deletions rust/agama-lib/src/manager.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
progress::Progress,
proxies::{Manager1Proxy, ProgressProxy},
};
use serde::Serialize;
use serde_repr::Serialize_repr;
use tokio_stream::StreamExt;
use zbus::Connection;

Expand All @@ -19,7 +19,9 @@ pub struct ManagerClient<'a> {
}

/// Represents the installation phase.
#[derive(Clone, Copy, Debug, PartialEq, Serialize, utoipa::ToSchema)]
/// NOTE: does this conversion have any value?
#[derive(Clone, Copy, Debug, PartialEq, Serialize_repr, utoipa::ToSchema)]
#[repr(u32)]
pub enum InstallationPhase {
/// Start up phase.
Startup,
Expand Down
2 changes: 1 addition & 1 deletion rust/agama-locale-data/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ pub mod timezone_part;

use keyboard::xkeyboard;

pub use locale::{InvalidKeymap, InvalidLocaleCode, KeymapId, LocaleCode};
pub use locale::{InvalidKeymap, InvalidLocaleCode, KeymapId, LocaleId};

fn file_reader(file_path: &str) -> anyhow::Result<impl BufRead> {
let file = File::open(file_path)
Expand Down
8 changes: 4 additions & 4 deletions rust/agama-locale-data/src/locale.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,15 @@ use std::{fmt::Display, str::FromStr};
use thiserror::Error;

#[derive(Clone, Debug, PartialEq, Serialize)]
pub struct LocaleCode {
pub struct LocaleId {
// ISO-639
pub language: String,
// ISO-3166
pub territory: String,
pub encoding: String,
}

impl Display for LocaleCode {
impl Display for LocaleId {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
Expand All @@ -25,7 +25,7 @@ impl Display for LocaleCode {
}
}

impl Default for LocaleCode {
impl Default for LocaleId {
fn default() -> Self {
Self {
language: "en".to_string(),
Expand All @@ -39,7 +39,7 @@ impl Default for LocaleCode {
#[error("Not a valid locale string: {0}")]
pub struct InvalidLocaleCode(String);

impl TryFrom<&str> for LocaleCode {
impl TryFrom<&str> for LocaleId {
type Error = InvalidLocaleCode;

fn try_from(value: &str) -> Result<Self, Self::Error> {
Expand Down
16 changes: 8 additions & 8 deletions rust/agama-server/src/l10n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ mod timezone;
pub mod web;

use crate::error::Error;
use agama_locale_data::{KeymapId, LocaleCode};
use agama_locale_data::{KeymapId, LocaleId};
use anyhow::Context;
use keyboard::KeymapsDatabase;
use locale::LocalesDatabase;
Expand All @@ -26,7 +26,7 @@ pub struct Locale {
pub locales_db: LocalesDatabase,
keymap: KeymapId,
keymaps_db: KeymapsDatabase,
ui_locale: LocaleCode,
ui_locale: LocaleId,
pub ui_keymap: KeymapId,
}

Expand All @@ -48,7 +48,7 @@ impl Locale {
.iter()
.map(|l| {
(
l.code.to_string(),
l.id.to_string(),
l.language.to_string(),
l.territory.to_string(),
)
Expand Down Expand Up @@ -82,7 +82,7 @@ impl Locale {

#[dbus_interface(property, name = "UILocale")]
fn set_ui_locale(&mut self, locale: &str) -> zbus::fdo::Result<()> {
let locale: LocaleCode = locale
let locale: LocaleId = locale
.try_into()
.map_err(|_e| zbus::fdo::Error::Failed(format!("Invalid locale value '{locale}'")))?;
helpers::set_service_locale(&locale);
Expand Down Expand Up @@ -191,7 +191,7 @@ impl Locale {
}

impl Locale {
pub fn new_with_locale(ui_locale: &LocaleCode) -> Result<Self, Error> {
pub fn new_with_locale(ui_locale: &LocaleId) -> Result<Self, Error> {
const DEFAULT_TIMEZONE: &str = "Europe/Berlin";

let locale = ui_locale.to_string();
Expand All @@ -201,7 +201,7 @@ impl Locale {
let mut default_locale = ui_locale.to_string();
if !locales_db.exists(locale.as_str()) {
// TODO: handle the case where the database is empty (not expected!)
default_locale = locales_db.entries().first().unwrap().code.to_string();
default_locale = locales_db.entries().first().unwrap().id.to_string();
};

let mut timezones_db = TimezonesDatabase::new();
Expand Down Expand Up @@ -231,7 +231,7 @@ impl Locale {
Ok(locale)
}

pub fn translate(&mut self, locale: &LocaleCode) -> Result<(), Error> {
pub fn translate(&mut self, locale: &LocaleId) -> Result<(), Error> {
self.timezones_db.read(&locale.language)?;
self.locales_db.read(&locale.language)?;
self.ui_locale = locale.clone();
Expand Down Expand Up @@ -259,7 +259,7 @@ impl Locale {

pub async fn export_dbus_objects(
connection: &Connection,
locale: &LocaleCode,
locale: &LocaleId,
) -> Result<(), Box<dyn std::error::Error>> {
const PATH: &str = "/org/opensuse/Agama1/Locale";

Expand Down
8 changes: 4 additions & 4 deletions rust/agama-server/src/l10n/helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,16 @@
//!
//! FIXME: find a better place for the localization function

use agama_locale_data::LocaleCode;
use agama_locale_data::LocaleId;
use gettextrs::{bind_textdomain_codeset, setlocale, textdomain, LocaleCategory};
use std::env;

/// Initializes the service locale.
///
/// It returns the used locale. Defaults to `en_US.UTF-8`.
pub fn init_locale() -> Result<LocaleCode, Box<dyn std::error::Error>> {
pub fn init_locale() -> Result<LocaleId, Box<dyn std::error::Error>> {
let lang = env::var("LANG").unwrap_or("en_US.UTF-8".to_string());
let locale: LocaleCode = lang.as_str().try_into().unwrap_or_default();
let locale: LocaleId = lang.as_str().try_into().unwrap_or_default();

set_service_locale(&locale);
textdomain("xkeyboard-config")?;
Expand All @@ -21,7 +21,7 @@ pub fn init_locale() -> Result<LocaleCode, Box<dyn std::error::Error>> {

/// Sets the service locale.
///
pub fn set_service_locale(locale: &LocaleCode) {
pub fn set_service_locale(locale: &LocaleId) {
if setlocale(LocaleCategory::LcAll, locale.to_string()).is_none() {
log::warn!("Could not set the locale");
}
Expand Down
3 changes: 3 additions & 0 deletions rust/agama-server/src/l10n/keyboard.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use agama_locale_data::{get_localectl_keymaps, keyboard::XkbConfigRegistry, KeymapId};
use gettextrs::*;
use serde::Serialize;
use serde_with::{serde_as, DisplayFromStr};
use std::collections::HashMap;

#[serde_as]
// Minimal representation of a keymap
#[derive(Clone, Debug, Serialize, utoipa::ToSchema)]
pub struct Keymap {
/// Keymap identifier (e.g., "us")
#[serde_as(as = "DisplayFromStr")]
pub id: KeymapId,
/// Keymap description
description: String,
Expand Down
20 changes: 10 additions & 10 deletions rust/agama-server/src/l10n/locale.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! This module provides support for reading the locales database.

use crate::error::Error;
use agama_locale_data::{InvalidLocaleCode, LocaleCode};
use agama_locale_data::{InvalidLocaleCode, LocaleId};
use anyhow::Context;
use serde::Serialize;
use serde_with::{serde_as, DisplayFromStr};
Expand All @@ -13,7 +13,7 @@ use std::process::Command;
pub struct LocaleEntry {
/// The locale code (e.g., "es_ES.UTF-8").
#[serde_as(as = "DisplayFromStr")]
pub code: LocaleCode,
pub id: LocaleId,
/// Localized language name (e.g., "Spanish", "Español", etc.)
pub language: String,
/// Localized territory name (e.g., "Spain", "España", etc.)
Expand All @@ -26,7 +26,7 @@ pub struct LocaleEntry {
/// translations are obtained from the `agama_locale_data` crate.
#[derive(Default)]
pub struct LocalesDatabase {
known_locales: Vec<LocaleCode>,
known_locales: Vec<LocaleId>,
locales: Vec<LocaleEntry>,
}

Expand All @@ -47,7 +47,7 @@ impl LocalesDatabase {
String::from_utf8(result.stdout).context("Invalid UTF-8 sequence from list-locales")?;
self.known_locales = output
.lines()
.filter_map(|line| TryInto::<LocaleCode>::try_into(line).ok())
.filter_map(|line| TryInto::<LocaleId>::try_into(line).ok())
.collect();
self.locales = self.get_locales(ui_language)?;
Ok(())
Expand All @@ -56,10 +56,10 @@ impl LocalesDatabase {
/// Determines whether a locale exists in the database.
pub fn exists<T>(&self, locale: T) -> bool
where
T: TryInto<LocaleCode>,
T: TryInto<LocaleId>,
T::Error: Into<InvalidLocaleCode>,
{
if let Ok(locale) = TryInto::<LocaleCode>::try_into(locale) {
if let Ok(locale) = TryInto::<LocaleId>::try_into(locale) {
return self.known_locales.contains(&locale);
}

Expand Down Expand Up @@ -101,7 +101,7 @@ impl LocalesDatabase {
.unwrap_or(territory.id.to_string());

let entry = LocaleEntry {
code: code.clone(),
id: code.clone(),
language: language_label,
territory: territory_label,
};
Expand All @@ -115,15 +115,15 @@ impl LocalesDatabase {
#[cfg(test)]
mod tests {
use super::LocalesDatabase;
use agama_locale_data::LocaleCode;
use agama_locale_data::LocaleId;

#[test]
fn test_read_locales() {
let mut db = LocalesDatabase::new();
db.read("de").unwrap();
let found_locales = db.entries();
let spanish: LocaleCode = "es_ES".try_into().unwrap();
let found = found_locales.iter().find(|l| l.code == spanish).unwrap();
let spanish: LocaleId = "es_ES".try_into().unwrap();
let found = found_locales.iter().find(|l| l.id == spanish).unwrap();
assert_eq!(&found.language, "Spanisch");
assert_eq!(&found.territory, "Spanien");
}
Expand Down
16 changes: 8 additions & 8 deletions rust/agama-server/src/l10n/web.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use crate::{
l10n::helpers,
web::{Event, EventsSender},
};
use agama_locale_data::{InvalidKeymap, LocaleCode};
use agama_locale_data::{InvalidKeymap, LocaleId};
use axum::{
extract::State,
http::StatusCode,
Expand Down Expand Up @@ -55,8 +55,8 @@ struct LocaleState {
///
/// * `events`: channel to send the events to the main service.
pub fn l10n_service(events: EventsSender) -> Router {
let code = LocaleCode::default();
let locale = Locale::new_with_locale(&code).unwrap();
let id = LocaleId::default();
let locale = Locale::new_with_locale(&id).unwrap();
let state = LocaleState {
locale: Arc::new(RwLock::new(locale)),
events,
Expand Down Expand Up @@ -147,7 +147,7 @@ async fn set_config(
}

if let Some(ui_locale) = &value.ui_locale {
let locale: LocaleCode = ui_locale
let locale: LocaleId = ui_locale
.as_str()
.try_into()
.map_err(|_e| LocaleError::UnknownLocale(ui_locale.to_string()))?;
Expand Down Expand Up @@ -193,13 +193,13 @@ async fn get_config(State(state): State<LocaleState>) -> Json<LocaleConfig> {
#[cfg(test)]
mod tests {
use crate::l10n::{web::LocaleState, Locale};
use agama_locale_data::{KeymapId, LocaleCode};
use agama_locale_data::{KeymapId, LocaleId};
use std::sync::{Arc, RwLock};
use tokio::{sync::broadcast::channel, test};

fn build_state() -> LocaleState {
let (tx, _) = channel(16);
let default_code = LocaleCode::default();
let default_code = LocaleId::default();
let locale = Locale::new_with_locale(&default_code).unwrap();
LocaleState {
locale: Arc::new(RwLock::new(locale)),
Expand All @@ -211,8 +211,8 @@ mod tests {
async fn test_locales() {
let state = build_state();
let response = super::locales(axum::extract::State(state)).await;
let default = LocaleCode::default();
let found = response.iter().find(|l| l.code == default);
let default = LocaleId::default();
let found = response.iter().find(|l| l.id == default);
assert!(found.is_some());
}

Expand Down
2 changes: 1 addition & 1 deletion rust/agama-server/tests/l10n.rs
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ async fn test_keymaps() {
let response = service.oneshot(request).await.unwrap();
assert_eq!(response.status(), StatusCode::OK);
let body = body_to_string(response.into_body()).await;
assert!(body.contains(r#""layout":"us""#));
assert!(body.contains(r#""id":"us""#));
}

#[test]
Expand Down
23 changes: 8 additions & 15 deletions web/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ import { Outlet } from "react-router-dom";

import { useInstallerClient, useInstallerClientStatus } from "~/context/installer";
import { useProduct } from "./context/product";
import { CONFIG, INSTALL, STARTUP } from "~/client/phase";
import { BUSY, IDLE } from "~/client/status";
import { INSTALL, STARTUP } from "~/client/phase";
import { BUSY } from "~/client/status";

import { DBusError, If, Installation } from "~/components/core";
import { Loading } from "./components/layout";
Expand All @@ -51,29 +51,22 @@ function App() {

useEffect(() => {
if (client) {
// FIXME: adapt to the new API
// return client.manager.onPhaseChange(setPhase);
setPhase(CONFIG);
return client.manager.onPhaseChange(setPhase);
}
}, [client, setPhase]);

useEffect(() => {
if (client) {
setStatus(IDLE);
// FIXME: adapt to the new API
// return client.manager.onStatusChange(setStatus);
return client.manager.onStatusChange(setStatus);
}
}, [client, setStatus]);

useEffect(() => {
const loadPhase = async () => {
setPhase(CONFIG);
setStatus(IDLE);
// FIXME: adapt to the new API
// const phase = await client.manager.getPhase();
// const status = await client.manager.getStatus();
// setPhase(phase);
// setStatus(status);
const phase = await client.manager.getPhase();
const status = await client.manager.getStatus();
setPhase(phase);
setStatus(status);
};

if (client) {
Expand Down
Loading
Loading