From 479903d0fb987daa127b04d88b8adfd27c51f536 Mon Sep 17 00:00:00 2001 From: NSoiffer Date: Wed, 7 Feb 2024 06:52:25 +0000 Subject: [PATCH] Add a preference `CheckRuleFiles ` with values `All`, `Prefs`, and `None` to tell MathCAT whether it should check the Rule files on every call (set_mathml, get speech/braille, or navigate) to see if they have changed. Fixes #249 --- src/prefs.rs | 70 +++++++++++++++++++++++++++++++----------------- src/speech.rs | 73 +++++++++++++++++++++++++++++++-------------------- 2 files changed, 90 insertions(+), 53 deletions(-) diff --git a/src/prefs.rs b/src/prefs.rs index 803ec607..c30a74f9 100644 --- a/src/prefs.rs +++ b/src/prefs.rs @@ -90,6 +90,7 @@ impl Preferences{ prefs.insert("CapitalLetters_Pitch".to_string(), Yaml::Real("0.0".to_string())); prefs.insert("CapitalLetters_Beep".to_string(), Yaml::Boolean(false)); prefs.insert("IntentErrorRecovery".to_string(), Yaml::String("IgnoreIntent".to_string())); // also Error + prefs.insert("CheckRuleFiles".to_string(), Yaml::String("All".to_string())); // avoid checking for rule files being changed (40% speedup!) (All, Prefs, None) return Preferences{ prefs }; } @@ -249,7 +250,7 @@ impl PreferenceManager { /// If rules_dir is an empty PathBuf, the existing rules_dir is used (an error if it doesn't exist) pub fn initialize(&mut self, rules_dir: PathBuf) -> Result<()> { self.set_rules_dir(&rules_dir)?; - self.set_preferences()?; + self.set_preference_files()?; self.set_all_files(&rules_dir)?; return Ok( () ); } @@ -282,7 +283,7 @@ impl PreferenceManager { /// Read the preferences from the files (if not up to date) and set the preferences and preference files /// Returns failure if the files don't exist or have errors - fn set_preferences(&mut self) -> Result<()> { + pub fn set_preference_files(&mut self) -> Result<()> { // first, read in the preferences -- need to determine which files to read next // the prefs files are in the rules dir and the user dir; differs from other files if self.api_prefs.prefs.is_empty() { @@ -325,6 +326,7 @@ impl PreferenceManager { }; bail!("Didn't find preferences in rule directory ('{}') or user directory ('{}')", &system_prefs_file.to_string_lossy(), user_prefs_file_name); } + self.set_based_on_changes(&prefs)?; self.user_prefs = prefs; return Ok( () ); } @@ -378,6 +380,36 @@ impl PreferenceManager { return Ok( () ); } + /// If some preferences have changed, we may need to recompute other ones + /// The key prefs are Language, SpeechStyle, and BrailleCode + fn set_based_on_changes(&mut self, new_prefs: &Preferences) -> Result<()> { + let old_language = self.user_prefs.prefs.get("Language"); // not set if first time + if old_language.is_none() { + return Ok( () ); // if "Language" isn't set yet, nothing else is either -- first time through, so no updating needed. + } + let old_language = old_language.unwrap(); + let new_language = new_prefs.prefs.get("Language").unwrap(); + if old_language != new_language { + let language_dir = self.rules_dir.to_path_buf().join("Languages"); + self.set_speech_files(&language_dir, new_language.as_str().unwrap())?; // also sets style file + } else { + let old_speech_style = self.user_prefs.prefs.get("SpeechStyle").unwrap(); + let new_speech_style = new_prefs.prefs.get("SpeechStyle").unwrap(); + let language_dir = self.rules_dir.to_path_buf().join("Languages"); + if old_speech_style != new_speech_style { + self.set_speech_files(&language_dir, new_speech_style.as_str().unwrap())?; + } + } + + let old_braille_code = self.user_prefs.prefs.get("BrailleCode").unwrap(); + let new_braille_code = new_prefs.prefs.get("BrailleCode").unwrap(); + if old_braille_code != new_braille_code { + let braille_code_dir = self.rules_dir.to_path_buf().join("Braille"); + self.set_braille_files(&braille_code_dir, new_braille_code.as_str().unwrap())?; // also sets style file + } + + return Ok( () ); + } /// Find a file matching `file_name` by starting in the regional directory and looking to the language. /// If that fails, fall back to looking for the default repeating the same process -- something needs to be found or MathCAT crashes @@ -549,24 +581,29 @@ impl PreferenceManager { panic!("Internal error: set_api_string_pref called on invalid PreferenceManager -- error message\n{}", &self.error); }; + self.reset_preferences(key, value)?; + self.api_prefs.prefs.insert(key.to_string(), Yaml::String(value.to_string())); + + return Ok( () ); + } + + fn reset_preferences(&mut self, changed_pref: &str, changed_value: &str) -> Result<()> { let language_dir = self.rules_dir.to_path_buf().join("Languages"); - match key { + match changed_pref { "Language" => { - self.set_speech_files(&language_dir, value)? + self.set_speech_files(&language_dir, changed_value)? }, "SpeechStyle" => { let language = self.pref_to_string("Language"); let language = if language.as_str() == "Auto" {"en"} else {language.as_str()}; // avoid 'temp value dropped while borrowed' error - self.set_style_file(&language_dir, language, value)? + self.set_style_file(&language_dir, language, changed_value)? }, "BrailleCode" => { let braille_dir = self.rules_dir.to_path_buf().join("Braille"); - self.set_braille_files(&braille_dir, value)? + self.set_braille_files(&braille_dir, changed_value)? }, _ => (), } - self.api_prefs.prefs.insert(key.to_string(), Yaml::String(value.to_string())); - return Ok( () ); } @@ -640,22 +677,7 @@ impl PreferenceManager { panic!("Internal error: set_user_prefs called on invalid PreferenceManager -- error message\n{}", &self.error); }; - let language_dir = self.rules_dir.to_path_buf().join("Languages"); - match key { - "Language" => { - self.set_speech_files(&language_dir, value)?; - }, - "SpeechStyle" => { - let language = self.pref_to_string("Language"); - let language = if language.as_str() == "Auto" {"en"} else {language.as_str()}; // avoid 'temp value dropped while borrowed' error - self.set_style_file(&language_dir, language, value)?; - }, - "BrailleCode" => { - let braille_dir = self.rules_dir.to_path_buf().join("Braille"); - self.set_braille_files(&braille_dir, value)?; - }, - _ => (), - } + self.reset_preferences(key, value)?; self.user_prefs.set_string_value(key, value); return Ok(()); } diff --git a/src/speech.rs b/src/speech.rs index 3800819c..34a9d68f 100644 --- a/src/speech.rs +++ b/src/speech.rs @@ -1916,15 +1916,16 @@ impl FilesAndTimes { } /// Returns true if the main file matches the corresponding preference location and files' times are all current - pub fn is_file_up_to_date(&self, pref_path: &Path) -> bool { - if cfg!(target_family = "wasm") { - return !self.ft.is_empty(); - } + pub fn is_file_up_to_date(&self, pref_path: &Path, should_ignore_file_time: bool) -> bool { // if the time isn't set or the path is different from the prefernce (which might have changed), return false if self.ft.is_empty() || self.ft[0].time == SystemTime::UNIX_EPOCH || self.as_path() != pref_path { return false; } + if should_ignore_file_time || cfg!(target_family = "wasm") { + return !self.ft.is_empty(); + } + // check the time stamp on the included files -- if the head file hasn't changed, the the paths for the included files will the same for file in &self.ft { @@ -2105,8 +2106,13 @@ impl SpeechRules { } pub fn read_files(&mut self) -> Result<()> { + let check_rule_files = self.pref_manager.borrow().pref_to_string("CheckRuleFiles"); + if check_rule_files != "None" { // "Prefs" or "All" are other values + self.pref_manager.borrow_mut().set_preference_files()?; + } + let should_ignore_file_time = self.pref_manager.borrow().pref_to_string("CheckRuleFiles") != "All"; // ignore for "None", "Prefs" let rule_file = self.pref_manager.borrow().get_rule_file(&self.name).to_path_buf(); // need to create PathBuf to avoid a move/use problem - if self.rules.is_empty() || !self.rule_files.is_file_up_to_date(&rule_file) { + if self.rules.is_empty() || !self.rule_files.is_file_up_to_date(&rule_file, should_ignore_file_time) { self.rules.clear(); let files_read = self.read_patterns(&rule_file)?; self.rule_files.set_files_and_times(files_read); @@ -2115,12 +2121,15 @@ impl SpeechRules { let pref_manager = self.pref_manager.borrow(); let unicode_pref_files = if self.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()}; - if !self.unicode_short_files.borrow().is_file_up_to_date(unicode_pref_files.0) { + if !self.unicode_short_files.borrow().is_file_up_to_date(unicode_pref_files.0, should_ignore_file_time) { self.unicode_short.borrow_mut().clear(); self.unicode_short_files.borrow_mut().set_files_and_times(self.read_unicode(None, true)?); } - if !self.definitions_files.borrow().is_file_up_to_date(pref_manager.get_definitions_file(self.name != RulesFor::Braille)) { + if self.definitions_files.borrow().ft.is_empty() || !self.definitions_files.borrow().is_file_up_to_date( + pref_manager.get_definitions_file(self.name != RulesFor::Braille), + should_ignore_file_time + ) { self.definitions_files.borrow_mut().set_files_and_times(read_definitions_file(self.name != RulesFor::Braille)?); } return Ok( () ); @@ -2545,8 +2554,9 @@ impl<'c, 's:'c, 'r, 'm:'c> SpeechRulesWithContext<'c, 's,'m> { if replacements.is_none() { // see if it in the full unicode table (if it isn't loaded already) let pref_manager = rules.pref_manager.borrow(); - let unicode_pref_files = if rules.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()}; - if rules.unicode_full.borrow().is_empty() || !rules.unicode_full_files.borrow().is_file_up_to_date(unicode_pref_files.1) { + let unicode_pref_files = if rules.name == RulesFor::Braille {pref_manager.get_braille_unicode_file()} else {pref_manager.get_speech_unicode_file()}; + let should_ignore_file_time = pref_manager.pref_to_string("CheckRuleFiles") == "All"; + if rules.unicode_full.borrow().is_empty() || !rules.unicode_full_files.borrow().is_file_up_to_date(unicode_pref_files.1, should_ignore_file_time) { info!("*** Loading full unicode {} for char '{}'/{:#06x}", rules.name, ch, ch_as_u32); rules.unicode_full.borrow_mut().clear(); rules.unicode_full_files.borrow_mut().set_files_and_times(rules.read_unicode(None, false)?); @@ -2703,9 +2713,6 @@ mod tests { #[test] fn test_up_to_date() { use crate::interface::*; - use std::fs; - use std::thread::sleep; - use std::time::Duration; // initialize and move to a directory where making a time change doesn't really matter set_rules_dir(super::super::abs_rules_dir_path()).unwrap(); set_preference("Language".to_string(), "zz-aa".to_string()).unwrap(); @@ -2715,25 +2722,33 @@ mod tests { panic!("Should not be an error in setting MathML") } - // files are read in due to setting the MathML -- record the time - SPEECH_RULES.with(|rules| { - let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone(); + set_preference("CheckRuleFiles".to_string(), "All".to_string()).unwrap(); + assert!(is_file_time_same(), "file's time did not get updated"); + set_preference("CheckRuleFiles".to_string(), "None".to_string()).unwrap(); + assert!(!is_file_time_same(), "file's time was wrongly updated (preference 'CheckRuleFiles' should have prevented updating)"); - // open the file, read all the contents, then write them back so the time changes - let contents = fs::read(&start_main_file.file).expect(&format!("Failed to read file {} during test", &start_main_file.file.to_string_lossy())); - #[allow(unused_must_use)] { - fs::write(start_main_file.file, contents); - sleep(Duration::from_millis(10)); - } + // change a file, cause read_files to be called, and return if MathCAT noticed the change and updated its time + fn is_file_time_same() -> bool { + // read and write a unicode file in a test dir + // files are read in due to setting the MathML - // speak should cause the file stored to have a new time - if let Err(e) = get_spoken_text() { - error!("{}", crate::errors_to_string(&e)); - panic!("Should not be an error in speech") - } - let updated_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone(); - assert!(start_main_file.time < updated_main_file.time); - }); + use std::time::Duration; + return SPEECH_RULES.with(|rules| { + let start_main_file = rules.borrow().unicode_short_files.borrow().ft[0].clone(); + + // open the file, read all the contents, then write them back so the time changes + let contents = std::fs::read(&start_main_file.file).expect(&format!("Failed to read file {} during test", &start_main_file.file.to_string_lossy())); + std::fs::write(start_main_file.file, contents).unwrap(); + std::thread::sleep(Duration::from_millis(5)); // pause a little to make sure the time changes + + // speak should cause the file stored to have a new time + if let Err(e) = get_spoken_text() { + error!("{}", crate::errors_to_string(&e)); + panic!("Should not be an error in speech") + } + return rules.borrow().unicode_short_files.borrow().ft[0].time == start_main_file.time; + }); + } } // #[test]