mod.rs (12824B)
1 mod asynchronous; 2 mod synchronous; 3 4 use crate::{ 5 env::ErrorReporter, 6 errors::L10nRegistrySetupError, 7 fluent::FluentBundle, 8 source::{FileSource, ResourceId}, 9 }; 10 use fluent_bundle::FluentResource; 11 use fluent_fallback::generator::BundleGenerator; 12 use rustc_hash::FxHashSet; 13 use std::{ 14 cell::{Ref, RefCell, RefMut}, 15 collections::HashSet, 16 rc::Rc, 17 }; 18 use unic_langid::LanguageIdentifier; 19 20 pub use asynchronous::GenerateBundles; 21 pub use synchronous::GenerateBundlesSync; 22 23 pub type FluentResourceSet = Vec<Rc<FluentResource>>; 24 25 /// The shared information that makes up the configuration the L10nRegistry. It is 26 /// broken out into a separate struct so that it can be shared via an Rc pointer. 27 #[derive(Default)] 28 struct Shared<P, B> { 29 metasources: RefCell<MetaSources>, 30 provider: P, 31 bundle_adapter: Option<B>, 32 } 33 34 /// [FileSources](FileSource) represent a single directory location to look for .ftl 35 /// files. These are Stored in a [Vec]. For instance, in a built version of Firefox with 36 /// the en-US locale, each [FileSource] may represent a different folder with many 37 /// different files. 38 /// 39 /// Firefox supports other *meta sources* for localization files in the form of language 40 /// packs which can be downloaded from the addon store. These language packs then would 41 /// be a separate metasource than the app' language. This [MetaSources] adds another [Vec] 42 /// over the [Vec] of [FileSources](FileSource) in order to provide a unified way to 43 /// iterate over all possible [FileSource] locations to finally obtain the final bundle. 44 /// 45 /// This structure uses an [Rc] to point to the [FileSource] so that a shallow copy 46 /// of these [FileSources](FileSource) can be obtained for iteration. This makes 47 /// it quick to copy the list of [MetaSources] for iteration, and guards against 48 /// invalidating that async nature of iteration when the underlying data mutates. 49 /// 50 /// Note that the async iteration of bundles is still only happening in one thread, 51 /// and is not multi-threaded. The processing is just split over time. 52 /// 53 /// The [MetaSources] are ultimately owned by the [Shared] in a [RefCell] so that the 54 /// source of truth can be mutated, and shallow copies of the [MetaSources] used for 55 /// iteration will be unaffected. 56 /// 57 /// Deriving [Clone] here is a relatively cheap operation, since the [Rc] will be cloned, 58 /// and point to the original [FileSource]. 59 #[derive(Default, Clone)] 60 pub struct MetaSources(Vec<Vec<Rc<FileSource>>>); 61 62 impl MetaSources { 63 /// Iterate over all FileSources in all MetaSources. 64 pub fn filesources(&self) -> impl Iterator<Item = &Rc<FileSource>> { 65 self.0.iter().flatten() 66 } 67 68 /// Iterate over all FileSources in all MetaSources. 69 pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Vec<Rc<FileSource>>> { 70 self.0.iter_mut() 71 } 72 73 /// The number of metasources. 74 pub fn len(&self) -> usize { 75 self.0.len() 76 } 77 78 /// If there are no metasources. 79 pub fn is_empty(&self) -> bool { 80 self.0.is_empty() 81 } 82 83 /// Clears out all metasources. 84 pub fn clear(&mut self) { 85 self.0.clear(); 86 } 87 88 /// Clears out only empty metasources. 89 pub fn clear_empty_metasources(&mut self) { 90 self.0.retain(|metasource| !metasource.is_empty()); 91 } 92 93 /// Adds a [FileSource] to its appropriate metasource. 94 pub fn add_filesource(&mut self, new_source: FileSource) { 95 if let Some(metasource) = self 96 .0 97 .iter_mut() 98 .find(|source| source[0].metasource == new_source.metasource) 99 { 100 // A metasource was found, add to the existing one. 101 metasource.push(Rc::new(new_source)); 102 } else { 103 // Create a new metasource. 104 self.0.push(vec![Rc::new(new_source)]); 105 } 106 } 107 108 /// Adds a [FileSource] to its appropriate metasource. 109 pub fn update_filesource(&mut self, new_source: &FileSource) -> bool { 110 if let Some(metasource) = self 111 .0 112 .iter_mut() 113 .find(|source| source[0].metasource == new_source.metasource) 114 { 115 if let Some(idx) = metasource.iter().position(|source| **source == *new_source) { 116 *metasource.get_mut(idx).unwrap() = Rc::new(new_source.clone()); 117 return true; 118 } 119 } 120 false 121 } 122 123 /// Get a metasource by index, but provide a nice error message if the index 124 /// is out of bounds. 125 pub fn get(&self, metasource_idx: usize) -> &Vec<Rc<FileSource>> { 126 if let Some(metasource) = self.0.get(metasource_idx) { 127 return &metasource; 128 } 129 panic!( 130 "Metasource index of {} is out of range of the list of {} meta sources.", 131 metasource_idx, 132 self.0.len() 133 ); 134 } 135 136 /// Get a [FileSource] from a metasource, but provide a nice error message if the 137 /// index is out of bounds. 138 pub fn filesource(&self, metasource_idx: usize, filesource_idx: usize) -> &FileSource { 139 let metasource = self.get(metasource_idx); 140 let reversed_idx = metasource.len() - 1 - filesource_idx; 141 if let Some(file_source) = metasource.get(reversed_idx) { 142 return file_source; 143 } 144 panic!( 145 "File source index of {} is out of range of the list of {} file sources.", 146 filesource_idx, 147 metasource.len() 148 ); 149 } 150 151 /// Get a [FileSource] by name from a metasource. This is useful for testing. 152 #[cfg(feature = "test-fluent")] 153 pub fn file_source_by_name(&self, metasource_idx: usize, name: &str) -> Option<&FileSource> { 154 use std::borrow::Borrow; 155 self.get(metasource_idx) 156 .iter() 157 .find(|&source| source.name == name) 158 .map(|source| source.borrow()) 159 } 160 161 /// Get an iterator for the [FileSources](FileSource) that match the [LanguageIdentifier] 162 /// and [ResourceId]. 163 #[cfg(feature = "test-fluent")] 164 pub fn get_sources_for_resource<'l>( 165 &'l self, 166 metasource_idx: usize, 167 langid: &'l LanguageIdentifier, 168 resource_id: &'l ResourceId, 169 ) -> impl Iterator<Item = &FileSource> { 170 use std::borrow::Borrow; 171 self.get(metasource_idx) 172 .iter() 173 .filter(move |source| source.has_file(langid, resource_id) != Some(false)) 174 .map(|source| source.borrow()) 175 } 176 } 177 178 /// The [BundleAdapter] can adapt the bundle to the environment with such actions as 179 /// setting the platform, and hooking up functions such as Fluent's DATETIME and 180 /// NUMBER formatting functions. 181 pub trait BundleAdapter { 182 fn adapt_bundle(&self, bundle: &mut FluentBundle); 183 } 184 185 /// The L10nRegistry is the main struct for owning the registry information. 186 /// 187 /// `P` - A provider 188 /// `B` - A bundle adapter 189 #[derive(Clone)] 190 pub struct L10nRegistry<P, B> { 191 shared: Rc<Shared<P, B>>, 192 } 193 194 impl<P, B> L10nRegistry<P, B> { 195 /// Create a new [L10nRegistry] from a provider. 196 pub fn with_provider(provider: P) -> Self { 197 Self { 198 shared: Rc::new(Shared { 199 metasources: Default::default(), 200 provider, 201 bundle_adapter: None, 202 }), 203 } 204 } 205 206 /// Set the bundle adapter. See [BundleAdapter] for more information. 207 pub fn set_bundle_adapter(&mut self, bundle_adapter: B) -> Result<(), L10nRegistrySetupError> 208 where 209 B: BundleAdapter, 210 { 211 let shared = Rc::get_mut(&mut self.shared).ok_or(L10nRegistrySetupError::RegistryLocked)?; 212 shared.bundle_adapter = Some(bundle_adapter); 213 Ok(()) 214 } 215 216 pub fn try_borrow_metasources(&self) -> Result<Ref<'_, MetaSources>, L10nRegistrySetupError> { 217 self.shared 218 .metasources 219 .try_borrow() 220 .map_err(|_| L10nRegistrySetupError::RegistryLocked) 221 } 222 223 pub fn try_borrow_metasources_mut( 224 &self, 225 ) -> Result<RefMut<'_, MetaSources>, L10nRegistrySetupError> { 226 self.shared 227 .metasources 228 .try_borrow_mut() 229 .map_err(|_| L10nRegistrySetupError::RegistryLocked) 230 } 231 232 /// Adds a new [FileSource] to the registry and to its appropriate metasource. If the 233 /// metasource for this [FileSource] does not exist, then it is created. 234 pub fn register_sources( 235 &self, 236 new_sources: Vec<FileSource>, 237 ) -> Result<(), L10nRegistrySetupError> { 238 for new_source in new_sources { 239 self.try_borrow_metasources_mut()? 240 .add_filesource(new_source); 241 } 242 Ok(()) 243 } 244 245 /// Update the information about sources already stored in the registry. Each 246 /// [FileSource] provided must exist, or else a [L10nRegistrySetupError] will 247 /// be returned. 248 pub fn update_sources( 249 &self, 250 new_sources: Vec<FileSource>, 251 ) -> Result<(), L10nRegistrySetupError> { 252 for new_source in new_sources { 253 if !self 254 .try_borrow_metasources_mut()? 255 .update_filesource(&new_source) 256 { 257 return Err(L10nRegistrySetupError::MissingSource { 258 name: new_source.name, 259 }); 260 } 261 } 262 Ok(()) 263 } 264 265 /// Remove the provided sources. If a metasource becomes empty after this operation, 266 /// the metasource is also removed. 267 pub fn remove_sources<S>(&self, del_sources: Vec<S>) -> Result<(), L10nRegistrySetupError> 268 where 269 S: ToString, 270 { 271 let del_sources: Vec<String> = del_sources.into_iter().map(|s| s.to_string()).collect(); 272 273 for metasource in self.try_borrow_metasources_mut()?.iter_mut() { 274 metasource.retain(|source| !del_sources.contains(&source.name)); 275 } 276 277 self.try_borrow_metasources_mut()?.clear_empty_metasources(); 278 279 Ok(()) 280 } 281 282 /// Clears out all metasources and sources. 283 pub fn clear_sources(&self) -> Result<(), L10nRegistrySetupError> { 284 self.try_borrow_metasources_mut()?.clear(); 285 Ok(()) 286 } 287 288 /// Flattens out all metasources and returns the complete list of source names. 289 pub fn get_source_names(&self) -> Result<Vec<String>, L10nRegistrySetupError> { 290 Ok(self 291 .try_borrow_metasources()? 292 .filesources() 293 .map(|s| s.name.clone()) 294 .collect()) 295 } 296 297 /// Checks if any metasources has a source, by the name. 298 pub fn has_source(&self, name: &str) -> Result<bool, L10nRegistrySetupError> { 299 Ok(self 300 .try_borrow_metasources()? 301 .filesources() 302 .any(|source| source.name == name)) 303 } 304 305 /// Get a [FileSource] by name by searching through all meta sources. 306 pub fn file_source_by_name( 307 &self, 308 name: &str, 309 ) -> Result<Option<FileSource>, L10nRegistrySetupError> { 310 Ok(self 311 .try_borrow_metasources()? 312 .filesources() 313 .find(|source| source.name == name) 314 .map(|source| (**source).clone())) 315 } 316 317 /// Returns a unique list of locale names from all sources. 318 pub fn get_available_locales(&self) -> Result<Vec<LanguageIdentifier>, L10nRegistrySetupError> { 319 let mut result = HashSet::new(); 320 let metasources = self.try_borrow_metasources()?; 321 for source in metasources.filesources() { 322 for locale in source.locales() { 323 result.insert(locale); 324 } 325 } 326 Ok(result.into_iter().map(|l| l.to_owned()).collect()) 327 } 328 } 329 330 /// Defines how to generate bundles synchronously and asynchronously. 331 impl<P, B> BundleGenerator for L10nRegistry<P, B> 332 where 333 P: ErrorReporter + Clone, 334 B: BundleAdapter + Clone, 335 { 336 type Resource = Rc<FluentResource>; 337 type Iter = GenerateBundlesSync<P, B>; 338 type Stream = GenerateBundles<P, B>; 339 type LocalesIter = std::vec::IntoIter<LanguageIdentifier>; 340 341 /// The synchronous version of the bundle generator. This is hooked into Gecko 342 /// code via the `l10nregistry_generate_bundles_sync` function. 343 fn bundles_iter( 344 &self, 345 locales: Self::LocalesIter, 346 resource_ids: FxHashSet<ResourceId>, 347 ) -> Self::Iter { 348 let resource_ids = resource_ids.into_iter().collect(); 349 self.generate_bundles_sync(locales, resource_ids) 350 } 351 352 /// The asynchronous version of the bundle generator. This is hooked into Gecko 353 /// code via the `l10nregistry_generate_bundles` function. 354 fn bundles_stream( 355 &self, 356 locales: Self::LocalesIter, 357 resource_ids: FxHashSet<ResourceId>, 358 ) -> Self::Stream { 359 let resource_ids = resource_ids.into_iter().collect(); 360 self.generate_bundles(locales, resource_ids) 361 .expect("Unable to get the MetaSources.") 362 } 363 }