Improve built-in services
This commit is contained in:
parent
b7ed5d1e1c
commit
ed5f96361b
9 changed files with 276 additions and 51 deletions
|
@ -1,5 +1,5 @@
|
|||
mod registry;
|
||||
pub use registry::{ServiceRegistry, StaticServiceRegistry};
|
||||
pub use registry::{/*ServiceRegistry, */StaticServiceRegistry};
|
||||
|
||||
mod websocket_stream;
|
||||
pub use websocket_stream::ws_stream;
|
||||
|
|
|
@ -37,7 +37,7 @@ impl<'a> ServiceRegistry<'a> {
|
|||
pub fn with_builtins() -> Self {
|
||||
let mut reg = Self::default();
|
||||
reg.register(crate::services::usdpl::DevToolsServer::new(crate::services_impl::DevTools{}))
|
||||
.register(crate::services::usdpl::TranslationsServer::new(crate::services_impl::Translations{}));
|
||||
.register(crate::services::usdpl::TranslationsServer::new(crate::services_impl::Translations::new()));
|
||||
reg
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,20 @@ impl<'a> generated::IDevTools<'a> for DevTools {
|
|||
lvl if lvl == generated::LogLevel::Error as _ => log::error!("{}", input.msg),
|
||||
lvl => return Err(Box::<dyn std::error::Error + Send + Sync>::from(format!("Unexpected input log level {}", lvl)))
|
||||
}
|
||||
Ok(generated::Empty{ ok: true })
|
||||
Ok(generated::Empty{})
|
||||
}
|
||||
|
||||
async fn echo(
|
||||
&mut self,
|
||||
input: generated::Ok,
|
||||
) -> Result<generated::Ok, Box<dyn std::error::Error + Send>> {
|
||||
Ok(input)
|
||||
}
|
||||
|
||||
async fn version(
|
||||
&mut self,
|
||||
_: generated::Empty
|
||||
) -> Result<generated::VersionString, Box<dyn std::error::Error + Send>> {
|
||||
Ok(generated::VersionString { version: env!("CARGO_PKG_VERSION").to_owned() })
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,30 @@
|
|||
use crate::services::usdpl as generated;
|
||||
|
||||
/// Built-in translation service implementation
|
||||
pub(crate) struct Translations {}
|
||||
pub(crate) struct Translations {
|
||||
catalogs: async_lock::RwLock<std::collections::HashMap<String, gettext_ng::Catalog>>,
|
||||
}
|
||||
|
||||
impl Translations {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
catalogs: async_lock::RwLock::new(std::collections::HashMap::new()),
|
||||
}
|
||||
}
|
||||
|
||||
async fn get_catalog(&self, lang: &str) -> Result<gettext_ng::Catalog, gettext_ng::Error> {
|
||||
let catalogs_rlock = self.catalogs.read().await;
|
||||
if let Some(catalog) = catalogs_rlock.get(lang) {
|
||||
Ok(catalog.to_owned())
|
||||
} else {
|
||||
drop(catalogs_rlock);
|
||||
let catalog = load_locale(lang)?;
|
||||
let mut catalogs_wlock = self.catalogs.write().await;
|
||||
catalogs_wlock.insert(lang.to_owned(), catalog.clone());
|
||||
Ok(catalog)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait::async_trait]
|
||||
impl<'a> generated::ITranslations<'a> for Translations {
|
||||
|
@ -9,12 +32,12 @@ impl<'a> generated::ITranslations<'a> for Translations {
|
|||
&mut self,
|
||||
input: generated::LanguageRequest,
|
||||
) -> Result<generated::TranslationsReply, Box<dyn std::error::Error + Send>> {
|
||||
let catalog = load_locale(&input.lang).map_err(|e| Box::new(e) as _)?;
|
||||
let catalog = self.get_catalog(&input.lang).await.map_err(|e| Box::new(e) as _)?;
|
||||
let catalog_map = catalog.nalltext();
|
||||
let mut map = std::collections::HashMap::with_capacity(catalog_map.len());
|
||||
for (key, val) in catalog_map.into_iter() {
|
||||
if val.len() > 1 {
|
||||
log::warn!("Translations key {} for language {} has plural entries which aren't currently supported", key, input.lang);
|
||||
log::warn!("Translations key {} for language {} has plural entries which aren't supported by Translations::get_language(...)", key, input.lang);
|
||||
}
|
||||
if let Some(val_0) = val.get(0) {
|
||||
map.insert(key.to_owned(), val_0.to_owned());
|
||||
|
@ -22,6 +45,20 @@ impl<'a> generated::ITranslations<'a> for Translations {
|
|||
}
|
||||
Ok(generated::TranslationsReply { translations: map })
|
||||
}
|
||||
|
||||
async fn get_translation(
|
||||
&mut self,
|
||||
input: generated::TranslationRequest,
|
||||
) -> Result<generated::TranslatedString, Box<dyn std::error::Error + Send>> {
|
||||
let catalog = self.get_catalog(&input.lang).await.map_err(|e| Box::new(e) as _)?;
|
||||
let translated = if input.count == 0 {
|
||||
catalog.gettext(&input.msg_id)
|
||||
} else {
|
||||
catalog.ngettext(&input.msg_id, &input.msg_id, input.count)
|
||||
}.to_owned();
|
||||
let is_default = translated == input.msg_id;
|
||||
Ok(generated::TranslatedString { translated, is_default, uuid: input.uuid })
|
||||
}
|
||||
}
|
||||
|
||||
fn load_locale(lang: &str) -> Result<gettext_ng::Catalog, gettext_ng::Error> {
|
||||
|
|
|
@ -2,10 +2,16 @@ syntax = "proto3";
|
|||
|
||||
package usdpl;
|
||||
|
||||
// The translation service
|
||||
// The tools service
|
||||
service DevTools {
|
||||
// Retrieves all translations for the provided 4-letter code
|
||||
// Write a message to the back-end log
|
||||
rpc Log (LogMessage) returns (Empty);
|
||||
|
||||
// Echo a message through the back-end
|
||||
rpc Echo (Ok) returns (Ok);
|
||||
|
||||
// Retrieve the USDPL version
|
||||
rpc Version (Empty) returns (VersionString);
|
||||
}
|
||||
|
||||
enum LogLevel {
|
||||
|
@ -22,6 +28,12 @@ message LogMessage {
|
|||
string msg = 2;
|
||||
}
|
||||
|
||||
message Empty {
|
||||
message Empty {}
|
||||
|
||||
message Ok {
|
||||
bool ok = 1;
|
||||
}
|
||||
|
||||
message VersionString {
|
||||
string version = 1;
|
||||
}
|
||||
|
|
|
@ -5,7 +5,9 @@ package usdpl;
|
|||
// The translation service
|
||||
service Translations {
|
||||
// Retrieves all translations for the provided 4-letter code
|
||||
rpc GetLanguage (LanguageRequest) returns (TranslationsReply) {}
|
||||
rpc GetLanguage (LanguageRequest) returns (TranslationsReply);
|
||||
/// Retrieve a specific translation
|
||||
rpc GetTranslation(TranslationRequest) returns (TranslatedString);
|
||||
}
|
||||
|
||||
// The request message containing the language code
|
||||
|
@ -17,3 +19,17 @@ message LanguageRequest {
|
|||
message TranslationsReply {
|
||||
map<string, string> translations = 1;
|
||||
}
|
||||
|
||||
// The request message for a specific translated string
|
||||
message TranslationRequest {
|
||||
string lang = 1;
|
||||
string msg_id = 2;
|
||||
uint64 count = 3;
|
||||
uint64 uuid = 4;
|
||||
}
|
||||
|
||||
message TranslatedString {
|
||||
string translated = 1;
|
||||
bool is_default = 2;
|
||||
uint64 uuid = 3;
|
||||
}
|
||||
|
|
|
@ -93,8 +93,36 @@ fn generate_service_methods(
|
|||
Some(x2.into_wasm())
|
||||
},
|
||||
Err(e) => {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -116,8 +144,36 @@ fn generate_service_methods(
|
|||
Some(x2.into_wasm())
|
||||
},
|
||||
Err(e) => {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -179,7 +235,35 @@ fn generate_service_methods(
|
|||
while let Some(next_result) = x.next().await {
|
||||
match next_result {
|
||||
Err(e) => {
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
},
|
||||
Ok(item) => {
|
||||
#[inline(always)]
|
||||
|
@ -208,8 +292,36 @@ fn generate_service_methods(
|
|||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -229,7 +341,35 @@ fn generate_service_methods(
|
|||
while let Some(next_result) = x.next().await {
|
||||
match next_result {
|
||||
Err(e) => {
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
},
|
||||
Ok(item) => {
|
||||
#[inline(always)]
|
||||
|
@ -258,8 +398,36 @@ fn generate_service_methods(
|
|||
}
|
||||
},
|
||||
Err(e) => {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
if let Some(e_handler) = &self.error_handler {
|
||||
let error_info = js_sys::Object::new();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("service"),
|
||||
&JsValue::from(self.service.descriptor())
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("method"),
|
||||
&JsValue::from(#method_name_str)
|
||||
).unwrap();
|
||||
js_sys::Reflect::set(
|
||||
&error_info,
|
||||
&JsValue::from("error"),
|
||||
&JsValue::from("todo"/*TODO*/)
|
||||
).unwrap();
|
||||
if let Err(call_e) = e_handler.call1(&JsValue::UNDEFINED, &error_info) {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}|js error:{}", self.service.descriptor(), #method_name_str, e,
|
||||
if let Some(s) = call_e.as_string() {
|
||||
s
|
||||
} else {
|
||||
format!("{:?}", call_e)
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// log error
|
||||
log::error!("service:{}|method:{}|error:{}", self.service.descriptor(), #method_name_str, e);
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -1088,11 +1256,12 @@ impl IServiceGenerator for WasmServiceGenerator {
|
|||
|
||||
#service_types
|
||||
|
||||
/// WASM/JS-compatible wrapper of the Rust nRPC service
|
||||
/// WASM/JS-compatible wrapper of a Rust nRPC service
|
||||
#[wasm_bindgen]
|
||||
pub struct #service_js_name {
|
||||
//#[wasm_bindgen(skip)]
|
||||
service: super::#service_struct_name<'static, WebSocketHandler>,
|
||||
error_handler: Option<js_sys::Function>,
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
|
@ -1106,9 +1275,15 @@ impl IServiceGenerator for WasmServiceGenerator {
|
|||
log::info!("Initialized ws service {} on port {}", #service_str_name, port);
|
||||
Self {
|
||||
service: implementation,
|
||||
error_handler: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn on_error(&mut self, handler: Option<js_sys::Function>) {
|
||||
self.error_handler = handler;
|
||||
}
|
||||
|
||||
#service_methods
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ mod api_decky;
|
|||
/// This contains functionality used in both the back-end and front-end.
|
||||
pub mod api {
|
||||
#[cfg(not(any(feature = "decky")))]
|
||||
#[allow(unused_imports)]
|
||||
pub use super::api_any::*;
|
||||
pub use super::api_common::*;
|
||||
#[cfg(all(feature = "decky", not(any(feature = "any"))))]
|
||||
|
|
|
@ -32,8 +32,6 @@ const DEFAULT_LOGGER: console_logs::BuiltInLogger = console_logs::BuiltInLogger:
|
|||
|
||||
static mut CACHE: Option<std::collections::HashMap<String, JsValue>> = None;
|
||||
|
||||
static mut TRANSLATIONS: Option<std::collections::HashMap<String, Vec<String>>> = None;
|
||||
|
||||
#[cfg(feature = "encrypt")]
|
||||
fn encryption_key() -> Vec<u8> {
|
||||
hex::decode(obfstr::obfstr!(env!("USDPL_ENCRYPTION_KEY"))).unwrap()
|
||||
|
@ -111,31 +109,3 @@ pub fn get_value(key: String) -> JsValue {
|
|||
.unwrap_or(JsValue::UNDEFINED)
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a phrase, equivalent to tr_n(msg_id, 0)
|
||||
#[wasm_bindgen]
|
||||
pub fn tr(msg_id: String) -> String {
|
||||
if let Some(translations) = unsafe { TRANSLATIONS.as_ref().unwrap().get(&msg_id) } {
|
||||
if let Some(translated) = translations.get(0) {
|
||||
translated.to_owned()
|
||||
} else {
|
||||
msg_id
|
||||
}
|
||||
} else {
|
||||
msg_id
|
||||
}
|
||||
}
|
||||
|
||||
/// Translate a phrase, retrieving the plural form for `n` items
|
||||
#[wasm_bindgen]
|
||||
pub fn tr_n(msg_id: String, n: usize) -> String {
|
||||
if let Some(translations) = unsafe { TRANSLATIONS.as_ref().unwrap().get(&msg_id) } {
|
||||
if let Some(translated) = translations.get(n) {
|
||||
translated.to_owned()
|
||||
} else {
|
||||
msg_id
|
||||
}
|
||||
} else {
|
||||
msg_id
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue