diff --git a/Cargo.toml b/Cargo.toml index 0afabc1..b7393bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,4 +12,5 @@ hidapi = "2.6.6" [features] access_hid_wrapper = [] +preset_file_saving = [] fast_controller_experimental = [] \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index 26efc8b..5d5dbd6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,6 +2,7 @@ mod color; mod hid_wrapper; mod keymap; mod rgb_controller; +mod tests; pub use color::Color; pub use hid_wrapper::HidWrapperError; @@ -13,5 +14,10 @@ pub use rgb_controller::effect_info::{ pub use rgb_controller::effects::Effects; pub use rgb_controller::preset::Preset; +#[cfg(feature = "preset_file_saving")] +mod save_preset; +#[cfg(feature = "preset_file_saving")] +pub use save_preset::{LoadPresetError, SavePresetError}; + #[cfg(feature = "access_hid_wrapper")] pub use hid_wrapper::HidWrapper; diff --git a/src/rgb_controller/preset.rs b/src/rgb_controller/preset.rs index df997e4..5084db7 100644 --- a/src/rgb_controller/preset.rs +++ b/src/rgb_controller/preset.rs @@ -32,6 +32,10 @@ impl Preset { keymap, } } + + pub fn is_valid(&self) -> bool { + self.brightness <= 16 && self.direction <= 3 && 1 <= self.speed && self.speed <= 16 + } } impl Default for Preset { diff --git a/src/save_preset.rs b/src/save_preset.rs new file mode 100644 index 0000000..9f779d0 --- /dev/null +++ b/src/save_preset.rs @@ -0,0 +1,149 @@ +use crate::{ + DirectionPolicy, EFFECTS, EffectInfo, Effects, Preset, + save_preset::{LoadPresetError::InvalidOrCorruptedFile, SavePresetError::InvalidPreset}, +}; +use std::{ + collections::VecDeque, + fs::File, + io::{Read, Write}, + vec, +}; + +#[derive(Debug)] +pub enum SavePresetError { + InvalidPreset, +} + +#[derive(Debug)] +pub enum LoadPresetError { + InvalidOrCorruptedFile, + OpenFileError(std::io::Error), +} + +const MAGIC_NUMBER_1: u8 = 0b10100100; +const MAGIC_NUMBER_2: u8 = 0b00010000; +const VERSION: u8 = 1; + +impl Preset { + pub fn save_to_file(&self, _file: &mut File) -> Result<(), SavePresetError> { + if !self.is_valid() { + return Err(SavePresetError::InvalidPreset); + } + _file.write_all(&self._encode_to_buffer()).expect(""); + Ok(()) + } + + pub(crate) fn _encode_to_buffer(&self) -> Vec { + let effect_info: EffectInfo = EFFECTS[usize::from(self.effect.to_u8() - 1)].clone(); + let mut buffer: Vec = vec![MAGIC_NUMBER_1, MAGIC_NUMBER_2, VERSION]; + + buffer + .push(self.effect.to_u8() + (u8::from(self.full_color) * 128) + (self.direction * 32)); //Effect id, full_color, and direction in a single byte + + if !self.full_color && effect_info.can_set_color { + buffer.extend(&self.color.to_array()); + } + + if effect_info.can_set_speed { + buffer.push(self.speed); + } + + buffer.push(self.brightness); + + if effect_info.requires_keymap { + for key_color in self.keymap.expect("keymap is Option::None").iter() { + buffer.extend(key_color); + } + } + + buffer + } + + pub fn load_from_file(_file: &mut File) -> Result { + let mut buffer: Vec = vec![]; + match _file.read_to_end(&mut buffer) { + Ok(_) => { + let mut buffer: VecDeque = buffer.into(); + + Self::_decode_from_buffer(&mut buffer) + } + Err(err) => return Err(LoadPresetError::OpenFileError(err)), + } + } + + pub(crate) fn _decode_from_buffer(buffer: &mut VecDeque) -> Result { + let mut save: Preset = Default::default(); + + if buffer.pop_front() != Some(MAGIC_NUMBER_1) || buffer.pop_front() != Some(MAGIC_NUMBER_2) + { + return Err(LoadPresetError::InvalidOrCorruptedFile); + } + + if buffer.pop_front().expect("") != VERSION { + return Err(LoadPresetError::InvalidOrCorruptedFile); + //todo!("TOO BAD, Invalid VERSION") + } + + if buffer[0] & 128 != 0 { + buffer[0] -= 128; + save.full_color = true; + } + + let direction: u8 = (buffer[0] >> 5) & 0b11; + buffer[0] -= direction * 32; + + save.effect = Effects::from_u8( + buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?, + ) + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + let effect_info: EffectInfo = EFFECTS[usize::from(save.effect.to_u8() - 1)].clone(); + + if !save.full_color && effect_info.can_set_color { + save.color.red = buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + save.color.green = buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + save.color.blue = buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + } + + if effect_info.can_set_direction != DirectionPolicy::None { + save.direction = direction; + } + + if effect_info.can_set_speed { + save.speed = buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + } + + save.brightness = buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?; + + if effect_info.requires_keymap { + let mut keymap_buffer: [[u8; 3]; 144] = [[0; 3]; 144]; + #[allow(clippy::needless_range_loop)] + for i in 0..keymap_buffer.len() { + keymap_buffer[i] = [ + buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?, + buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?, + buffer + .pop_front() + .ok_or(LoadPresetError::InvalidOrCorruptedFile)?, + ]; + } + save.keymap = Some(keymap_buffer); + } + Ok(save) + } +} diff --git a/src/tests.rs b/src/tests.rs new file mode 100644 index 0000000..2fad9fd --- /dev/null +++ b/src/tests.rs @@ -0,0 +1,2 @@ +#[cfg(feature = "preset_file_saving")] +mod preset_saving; diff --git a/src/tests/preset_saving.rs b/src/tests/preset_saving.rs new file mode 100644 index 0000000..6f92229 --- /dev/null +++ b/src/tests/preset_saving.rs @@ -0,0 +1,232 @@ +mod tests { + use std::collections::VecDeque; + + use crate::Keymap; + #[allow(unused_imports)] + use crate::Preset; + + #[test] + fn test_encode_decode() { + let preset = Preset::new( + crate::Effects::Static, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + true, + 6, + 1, + 0, + None, + ); + let mut buffer: VecDeque = preset._encode_to_buffer().into(); + assert_eq!( + preset, + Preset::_decode_from_buffer(&mut buffer).expect("error decoding") + ); + + let preset = Preset::new( + crate::Effects::VerticalWave, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + true, + 2, + 9, + 2, + None, + ); + let mut buffer: VecDeque = preset._encode_to_buffer().into(); + assert_eq!( + preset, + Preset::_decode_from_buffer(&mut buffer).expect("error decoding") + ); + + let preset = Preset::new( + crate::Effects::HorizontalWave, + crate::Color { + red: 67, + green: 69, + blue: 255, + }, + false, + 2, + 9, + 1, + None, + ); + let mut buffer: VecDeque = preset._encode_to_buffer().into(); + assert_eq!( + preset, + Preset::_decode_from_buffer(&mut buffer).expect("error decoding") + ); + + let mut keymap: Keymap = Default::default(); + keymap.set_all_keys(crate::Color { + red: 255, + green: 9, + blue: 7, + }); + keymap.set_key( + crate::KeymapKeys::C, + crate::Color { + red: 111, + green: 222, + blue: 133, + }, + ); + keymap.set_key( + crate::KeymapKeys::NumPlus, + crate::Color { + red: 112, + green: 223, + blue: 134, + }, + ); + let preset = Preset::new( + crate::Effects::StaticPerKey, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + false, + 16, + 1, + 0, + Some(keymap.map()), + ); + let mut buffer: VecDeque = preset._encode_to_buffer().into(); + assert_eq!( + preset, + Preset::_decode_from_buffer(&mut buffer).expect("error decoding") + ) + } + + #[cfg(target_os = "linux")] + #[test] + fn test_save_load() { + use std::fs::File; + + let preset = Preset::new( + crate::Effects::Static, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + true, + 6, + 1, + 0, + None, + ); + preset + .save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file")) + .expect("failed to save"); + assert_eq!( + preset, + Preset::load_from_file( + &mut File::open("/tmp/test_file.gk1k").expect("failed to open file") + ) + .expect("error loading from file") + ); + + let preset = Preset::new( + crate::Effects::VerticalWave, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + true, + 2, + 9, + 2, + None, + ); + preset + .save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file")) + .expect("failed to save"); + assert_eq!( + preset, + Preset::load_from_file( + &mut File::open("/tmp/test_file.gk1k").expect("failed to open file") + ) + .expect("error loading from file") + ); + + let preset = Preset::new( + crate::Effects::HorizontalWave, + crate::Color { + red: 67, + green: 69, + blue: 255, + }, + false, + 2, + 9, + 1, + None, + ); + preset + .save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file")) + .expect("failed to save"); + assert_eq!( + preset, + Preset::load_from_file( + &mut File::open("/tmp/test_file.gk1k").expect("failed to open file") + ) + .expect("error loading from file") + ); + + let mut keymap: Keymap = Default::default(); + keymap.set_all_keys(crate::Color { + red: 255, + green: 9, + blue: 7, + }); + keymap.set_key( + crate::KeymapKeys::C, + crate::Color { + red: 111, + green: 222, + blue: 133, + }, + ); + keymap.set_key( + crate::KeymapKeys::NumPlus, + crate::Color { + red: 112, + green: 223, + blue: 134, + }, + ); + let preset = Preset::new( + crate::Effects::StaticPerKey, + crate::Color { + red: 0, + green: 0, + blue: 0, + }, + false, + 16, + 1, + 0, + Some(keymap.map()), + ); + preset + .save_to_file(&mut File::create("/tmp/test_file.gk1k").expect("Failed to create file")) + .expect("failed to save"); + assert_eq!( + preset, + Preset::load_from_file( + &mut File::open("/tmp/test_file.gk1k").expect("failed to open file") + ) + .expect("error loading from file") + ); + } +}