/* * Copyright 2024 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //! Contains the DataStore, used to store input related data in a persistent way. use crate::input::KeyboardType; use log::{debug, error, info}; use serde::{Deserialize, Serialize}; use std::fs::File; use std::io::{Read, Write}; use std::path::Path; use std::sync::{Arc, RwLock}; /// Data store to be used to store information that persistent across device reboots. pub struct DataStore { file_reader_writer: Box, inner: Arc>, } #[derive(Default)] struct DataStoreInner { is_loaded: bool, data: Data, } #[derive(Default, Serialize, Deserialize)] struct Data { // Map storing data for keyboard classification for specific devices. #[serde(default)] keyboard_classifications: Vec, // NOTE: Important things to consider: // - Add any data that needs to be persisted here in this struct. // - Mark all new fields with "#[serde(default)]" for backward compatibility. // - Also, you can't modify the already added fields. // - Can add new nested fields to existing structs. e.g. Add another field to the struct // KeyboardClassification and mark it "#[serde(default)]". } #[derive(Default, Serialize, Deserialize)] struct KeyboardClassification { descriptor: String, keyboard_type: KeyboardType, is_finalized: bool, } impl DataStore { /// Creates a new instance of Data store pub fn new(file_reader_writer: Box) -> Self { Self { file_reader_writer, inner: Default::default() } } fn load(&mut self) { if self.inner.read().unwrap().is_loaded { return; } self.load_internal(); } fn load_internal(&mut self) { let s = self.file_reader_writer.read(); let data: Data = if !s.is_empty() { let deserialize: Data = match serde_json::from_str(&s) { Ok(deserialize) => deserialize, Err(msg) => { error!("Unable to deserialize JSON data into struct: {:?} -> {:?}", msg, s); Default::default() } }; deserialize } else { Default::default() }; let mut inner = self.inner.write().unwrap(); inner.data = data; inner.is_loaded = true; } fn save(&mut self) { let string_to_save; { let inner = self.inner.read().unwrap(); string_to_save = serde_json::to_string(&inner.data).unwrap(); } self.file_reader_writer.write(string_to_save); } /// Get keyboard type of the device (as stored in the data store) pub fn get_keyboard_type(&mut self, descriptor: &String) -> Option<(KeyboardType, bool)> { self.load(); let data = &self.inner.read().unwrap().data; for keyboard_classification in data.keyboard_classifications.iter() { if keyboard_classification.descriptor == *descriptor { return Some(( keyboard_classification.keyboard_type, keyboard_classification.is_finalized, )); } } None } /// Save keyboard type of the device in the data store pub fn set_keyboard_type( &mut self, descriptor: &String, keyboard_type: KeyboardType, is_finalized: bool, ) { { let data = &mut self.inner.write().unwrap().data; data.keyboard_classifications .retain(|classification| classification.descriptor != *descriptor); data.keyboard_classifications.push(KeyboardClassification { descriptor: descriptor.to_string(), keyboard_type, is_finalized, }) } self.save(); } } pub trait FileReaderWriter { fn read(&self) -> String; fn write(&self, to_write: String); } /// Default file reader writer implementation pub struct DefaultFileReaderWriter { filepath: String, } impl DefaultFileReaderWriter { /// Creates a new instance of Default file reader writer that can read and write string to a /// particular file in the filesystem pub fn new(filepath: String) -> Self { Self { filepath } } } impl FileReaderWriter for DefaultFileReaderWriter { fn read(&self) -> String { let path = Path::new(&self.filepath); let mut fs_string = String::new(); match File::open(path) { Err(e) => info!("couldn't open {:?}: {}", path, e), Ok(mut file) => match file.read_to_string(&mut fs_string) { Err(e) => error!("Couldn't read from {:?}: {}", path, e), Ok(_) => debug!("Successfully read from file {:?}", path), }, }; fs_string } fn write(&self, to_write: String) { let path = Path::new(&self.filepath); match File::create(path) { Err(e) => error!("couldn't create {:?}: {}", path, e), Ok(mut file) => match file.write_all(to_write.as_bytes()) { Err(e) => error!("Couldn't write to {:?}: {}", path, e), Ok(_) => debug!("Successfully saved to file {:?}", path), }, }; } } #[cfg(test)] mod tests { use crate::data_store::{ test_file_reader_writer::TestFileReaderWriter, DataStore, FileReaderWriter, }; use crate::input::KeyboardType; #[test] fn test_backward_compatibility_version_1() { // This test tests JSON string that will be created by the first version of data store // This test SHOULD NOT be modified let test_reader_writer = TestFileReaderWriter::new(); test_reader_writer.write(r#"{"keyboard_classifications":[{"descriptor":"descriptor","keyboard_type":{"type":"Alphabetic"},"is_finalized":true}]}"#.to_string()); let mut data_store = DataStore::new(Box::new(test_reader_writer)); let (keyboard_type, is_finalized) = data_store.get_keyboard_type(&"descriptor".to_string()).unwrap(); assert_eq!(keyboard_type, KeyboardType::Alphabetic); assert!(is_finalized); } } #[cfg(test)] pub mod test_file_reader_writer { use crate::data_store::FileReaderWriter; use std::sync::{Arc, RwLock}; #[derive(Default)] struct TestFileReaderWriterInner { fs_string: String, } #[derive(Default, Clone)] pub struct TestFileReaderWriter(Arc>); impl TestFileReaderWriter { pub fn new() -> Self { Default::default() } } impl FileReaderWriter for TestFileReaderWriter { fn read(&self) -> String { self.0.read().unwrap().fs_string.clone() } fn write(&self, fs_string: String) { self.0.write().unwrap().fs_string = fs_string; } } }