Skip to content

Commit

Permalink
Handle state changes mid async formats
Browse files Browse the repository at this point in the history
  • Loading branch information
zbraniecki committed Jul 7, 2021
1 parent f1f0766 commit 0f5955a
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 45 deletions.
2 changes: 1 addition & 1 deletion fluent-fallback/Cargo.toml
Expand Up @@ -21,11 +21,11 @@ categories = ["localization", "internationalization"]
chunky-vec = "0.1"
fluent-bundle = "0.15.1"
futures = "0.3"
once_cell = "1.7"
async-trait = "0.1"
unic-langid = { version = "0.9" }

[dev-dependencies]
fluent-langneg = "0.13"
unic-langid = { version = "0.9", features = ["macros"] }
fluent-resmgr = { path = "../fluent-resmgr" }
tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] }
108 changes: 68 additions & 40 deletions fluent-fallback/src/localization.rs
Expand Up @@ -3,9 +3,11 @@ use crate::env::LocalesProvider;
use crate::errors::LocalizationError;
use crate::generator::{BundleGenerator, BundleIterator, BundleStream};
use crate::types::{L10nAttribute, L10nKey, L10nMessage};
use chunky_vec::ChunkyVec;
use fluent_bundle::{FluentArgs, FluentBundle, FluentError};
use once_cell::sync::OnceCell;
use std::borrow::Cow;
use std::cell::RefCell;
use std::cell::UnsafeCell;

enum Bundles<G>
where
Expand Down Expand Up @@ -43,18 +45,23 @@ where
}
}

struct State {
dirty: bool,
sync: bool,
res_ids: Vec<String>,
}

pub struct Localization<G, P>
where
G: BundleGenerator<LocalesIter = P::Iter>,
P: LocalesProvider,
{
// Replace with `OneCell` once it stabilizes
// https://github.com/rust-lang/rust/issues/74465
bundles: OnceCell<Bundles<G>>,
// XXX: How to make us Drop the `Bundles` once all callers
// relying on it complete?
bundles: UnsafeCell<ChunkyVec<Bundles<G>>>,
generator: G,
provider: P,
res_ids: Vec<String>,
sync: bool,
state: RefCell<State>,
}

impl<G, P> Localization<G, P>
Expand All @@ -64,11 +71,14 @@ where
{
pub fn new(res_ids: Vec<String>, sync: bool) -> Self {
Self {
bundles: OnceCell::new(),
bundles: Default::default(),
generator: G::default(),
provider: P::default(),
res_ids,
sync,
state: RefCell::new(State {
dirty: false,
sync,
res_ids,
}),
}
}
}
Expand All @@ -80,49 +90,56 @@ where
{
pub fn with_env(res_ids: Vec<String>, sync: bool, provider: P, generator: G) -> Self {
Self {
bundles: OnceCell::new(),
bundles: Default::default(),
generator,
provider,
res_ids,
sync,
state: RefCell::new(State {
dirty: false,
sync,
res_ids,
}),
}
}

pub fn is_sync(&self) -> bool {
self.sync
self.state.borrow().sync
}

pub fn add_resource_id(&mut self, res_id: String) {
self.res_ids.push(res_id);
pub fn add_resource_id(&self, res_id: String) {
RefCell::borrow_mut(&self.state).res_ids.push(res_id);
self.on_change();
}

pub fn add_resource_ids(&mut self, res_ids: Vec<String>) {
self.res_ids.extend(res_ids);
pub fn add_resource_ids(&self, res_ids: Vec<String>) {
RefCell::borrow_mut(&self.state).res_ids.extend(res_ids);
self.on_change();
}

pub fn remove_resource_id(&mut self, res_id: String) -> usize {
self.res_ids.retain(|x| *x != res_id);
pub fn remove_resource_id(&self, res_id: String) -> usize {
RefCell::borrow_mut(&self.state)
.res_ids
.retain(|x| *x != res_id);
self.on_change();
self.res_ids.len()
RefCell::borrow(&self.state).res_ids.len()
}

pub fn remove_resource_ids(&mut self, res_ids: Vec<String>) -> usize {
self.res_ids.retain(|x| !res_ids.contains(x));
pub fn remove_resource_ids(&self, res_ids: Vec<String>) -> usize {
RefCell::borrow_mut(&self.state)
.res_ids
.retain(|x| !res_ids.contains(x));
self.on_change();
self.res_ids.len()
RefCell::borrow(&self.state).res_ids.len()
}

pub fn set_async(&mut self) {
if self.sync {
self.bundles.take();
self.sync = false;
pub fn set_async(&self) {
if self.state.borrow().sync {
RefCell::borrow_mut(&self.state).sync = false;
self.on_change();
}
}

pub fn on_change(&mut self) {
self.bundles.take();
pub fn on_change(&self) {
RefCell::borrow_mut(&self.state).dirty = true;
}

pub async fn format_value<'l>(
Expand Down Expand Up @@ -228,19 +245,30 @@ where
P: LocalesProvider,
{
fn get_bundles(&self) -> &Bundles<G> {
self.bundles.get_or_init(|| {
if self.sync {
Bundles::Iter(Cache::new(
self.generator
.bundles_iter(self.provider.locales(), self.res_ids.clone()),
))
unsafe {
let bundles = self.bundles.get();
if (*bundles).is_empty() || self.state.borrow().dirty {
RefCell::borrow_mut(&self.state).dirty = false;
let state = self.state.borrow();
let new_iter = {
if state.sync {
Bundles::Iter(Cache::new(
self.generator
.bundles_iter(self.provider.locales(), state.res_ids.clone()),
))
} else {
Bundles::Stream(AsyncCache::new(
self.generator
.bundles_stream(self.provider.locales(), state.res_ids.clone()),
))
}
};
(*bundles).push_get(new_iter)
} else {
Bundles::Stream(AsyncCache::new(
self.generator
.bundles_stream(self.provider.locales(), self.res_ids.clone()),
))
let len = (*bundles).len();
(*bundles).get(len - 1).unwrap()
}
})
}
}

fn format_message_from_bundle<'l>(
Expand Down
48 changes: 44 additions & 4 deletions fluent-fallback/tests/localization_test.rs
Expand Up @@ -93,10 +93,34 @@ impl futures::Stream for BundleIter {
type Item = FluentBundleResult<FluentResource>;

fn poll_next(
self: std::pin::Pin<&mut Self>,
mut self: std::pin::Pin<&mut Self>,
_cx: &mut std::task::Context<'_>,
) -> std::task::Poll<Option<Self::Item>> {
todo!()
if let Some(locale) = self.locales.next() {
let mut bundle = FluentBundle::new(vec![locale.clone()]);
bundle.set_use_isolating(false);

let mut errors = vec![];
for res_id in &self.res_ids {
let full_path = format!("./tests/resources/{}/{}", locale, res_id);
let source = fs::read_to_string(full_path).unwrap();
let res = match FluentResource::try_new(source) {
Ok(res) => res,
Err((res, err)) => {
errors.extend(err.into_iter().map(Into::into));
res
}
};
bundle.add_resource(res).unwrap();
}
if errors.is_empty() {
Some(Ok(bundle)).into()
} else {
Some(Err((bundle, errors))).into()
}
} else {
None.into()
}
}
}

Expand All @@ -112,8 +136,8 @@ impl BundleGenerator for ResourceManager {
BundleIter { locales, res_ids }
}

fn bundles_stream(&self, _locales: Self::LocalesIter, _res_ids: Vec<String>) -> Self::Stream {
todo!()
fn bundles_stream(&self, locales: Self::LocalesIter, res_ids: Vec<String>) -> Self::Stream {
BundleIter { locales, res_ids }
}
}

Expand Down Expand Up @@ -440,3 +464,19 @@ fn localization_format_missing_argument_error() {
},]
);
}

#[tokio::test]
async fn localization_handle_state_changes_mid_async() {
let resource_ids: Vec<String> = vec!["test.ftl".into()];
let locales = Locales::new(vec![langid!("en-US")]);
let res_mgr = ResourceManager;
let mut errors = vec![];

let loc = Localization::with_env(resource_ids, false, locales, res_mgr);

let future = loc.format_value("key", None, &mut errors);

loc.add_resource_id("test2.ftl".to_string());

future.await;
}

0 comments on commit 0f5955a

Please sign in to comment.