use serde::{Deserialize, Serialize};
use crate::ByteMultiple;
use crate::gui::types::conf::deserialize_or_default;
use crate::networking::types::data_representation::DataRepr;
use crate::notifications::types::sound::Sound;
#[derive(Clone, Serialize, Deserialize, PartialEq, Debug)]
#[serde(default)]
pub struct Notifications {
#[serde(deserialize_with = "deserialize_or_default")]
pub volume: u8,
#[serde(deserialize_with = "deserialize_or_default")]
pub data_notification: DataNotification,
#[serde(deserialize_with = "deserialize_or_default")]
pub favorite_notification: SimpleNotification,
#[serde(deserialize_with = "deserialize_or_default")]
pub ip_blacklist_notification: SimpleNotification,
#[allow(clippy::struct_field_names)]
#[serde(deserialize_with = "deserialize_or_default")]
pub remote_notifications: RemoteNotifications,
}
impl Default for Notifications {
fn default() -> Self {
Notifications {
volume: 50,
data_notification: DataNotification::default(),
favorite_notification: SimpleNotification {
is_active: false,
sound: Sound::Pop,
},
ip_blacklist_notification: SimpleNotification {
is_active: false,
sound: Sound::Swhoosh,
},
remote_notifications: RemoteNotifications::default(),
}
}
}
#[derive(Debug, Clone, Copy)]
pub enum Notification {
Data(DataNotification),
Favorite(SimpleNotification),
IpBlacklist(SimpleNotification),
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy)]
#[serde(default)]
pub struct DataNotification {
#[serde(deserialize_with = "deserialize_or_default")]
pub sound: Sound,
#[serde(deserialize_with = "deserialize_or_default")]
pub data_repr: DataRepr,
#[serde(deserialize_with = "deserialize_or_default")]
pub threshold: Option<u64>,
#[serde(deserialize_with = "deserialize_or_default")]
pub byte_multiple: ByteMultiple,
#[serde(deserialize_with = "deserialize_or_default")]
pub previous_threshold: u64,
}
impl Default for DataNotification {
fn default() -> Self {
DataNotification {
data_repr: DataRepr::default(),
threshold: None,
byte_multiple: ByteMultiple::KB,
sound: Sound::Gulp,
previous_threshold: 800_000,
}
}
}
impl DataNotification {
pub fn from(value: &str, existing: Option<Self>) -> Self {
let default = existing.unwrap_or_default();
let mut byte_multiple_inserted = ByteMultiple::B;
let chars: Vec<char> = value.trim().chars().collect();
let new_threshold = if chars.is_empty() {
0
} else if !chars.iter().map(|c| char::is_numeric(*c)).any(|x| !x) {
value.parse::<u64>().unwrap_or(default.previous_threshold)
} else {
let last_char = chars.last().unwrap_or(&' ');
byte_multiple_inserted = ByteMultiple::from_char(*last_char);
let without_multiple: String = chars[0..chars.len() - 1].iter().collect();
if without_multiple.parse::<u64>().is_ok()
&& TryInto::<u64>::try_into(
without_multiple.parse::<u128>().unwrap_or_default()
* u128::from(byte_multiple_inserted.multiplier()),
)
.is_ok()
{
without_multiple.parse::<u64>().unwrap_or_default()
* byte_multiple_inserted.multiplier()
} else if without_multiple.is_empty() {
byte_multiple_inserted = ByteMultiple::B;
0
} else {
byte_multiple_inserted = default.byte_multiple;
default.previous_threshold
}
};
Self {
threshold: Some(new_threshold),
previous_threshold: new_threshold,
byte_multiple: byte_multiple_inserted,
..default
}
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Copy, Default)]
#[serde(default)]
pub struct SimpleNotification {
#[serde(deserialize_with = "deserialize_or_default")]
pub is_active: bool,
#[serde(deserialize_with = "deserialize_or_default")]
pub sound: Sound,
}
impl SimpleNotification {
pub fn on(sound: Sound) -> Self {
Self {
is_active: true,
sound,
}
}
pub fn off(sound: Sound) -> Self {
Self {
is_active: false,
sound,
}
}
}
#[derive(Clone, Eq, PartialEq, Serialize, Deserialize, Debug, Default)]
#[serde(default)]
pub struct RemoteNotifications {
#[serde(deserialize_with = "deserialize_or_default")]
is_active: bool,
#[serde(deserialize_with = "deserialize_or_default")]
url: String,
}
impl RemoteNotifications {
pub fn is_active(&self) -> bool {
self.is_active
}
pub fn url(&self) -> &str {
&self.url
}
pub fn toggle(&mut self) {
self.is_active = !self.is_active;
}
pub fn set_url(&mut self, url: &str) {
self.url = url.trim().to_string();
}
pub fn is_active_and_set(&self) -> bool {
self.is_active && !self.url.is_empty()
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case("123",
DataNotification{
previous_threshold: 123, threshold: Some(123), byte_multiple: ByteMultiple::B, ..DataNotification::default() }
)]
#[case("500k",
DataNotification{
previous_threshold: 500_000, threshold: Some(500_000),byte_multiple: ByteMultiple::KB, ..DataNotification::default() }
)]
#[case("420m",
DataNotification{
previous_threshold: 420_000_000, threshold: Some(420_000_000),byte_multiple: ByteMultiple::MB, ..DataNotification::default() }
)]
#[case("744ь",
DataNotification{
previous_threshold: 744, threshold: Some(744),byte_multiple: ByteMultiple::B, ..DataNotification::default() }
)]
#[case("888g",
DataNotification{
previous_threshold: 888_000_000_000, threshold: Some(888_000_000_000),byte_multiple: ByteMultiple::GB, ..DataNotification::default() }
)]
fn test_can_instantiate_bytes_notification_from_string(
#[case] input: &str,
#[case] expected: DataNotification,
) {
assert_eq!(expected, DataNotification::from(input, None));
}
#[rstest]
#[case("foob@r")]
#[case("2O6")]
fn test_will_reuse_previous_value_if_cannot_parse(#[case] input: &str) {
let existing_notification = DataNotification {
previous_threshold: 420_000_000_000,
byte_multiple: ByteMultiple::GB,
..Default::default()
};
let expected = DataNotification {
previous_threshold: 420_000_000_000,
threshold: Some(420_000_000_000),
byte_multiple: ByteMultiple::GB,
..Default::default()
};
assert_eq!(
expected,
DataNotification::from(input, Some(existing_notification))
);
}
#[test]
fn test_can_instantiate_simple_notification() {
assert_eq!(
SimpleNotification::on(Sound::Gulp),
SimpleNotification {
is_active: true,
sound: Sound::Gulp
}
);
assert_eq!(
SimpleNotification::on(Sound::Swhoosh),
SimpleNotification {
is_active: true,
sound: Sound::Swhoosh
}
);
assert_eq!(
SimpleNotification::off(Sound::Pop),
SimpleNotification {
is_active: false,
sound: Sound::Pop
}
);
assert_eq!(
SimpleNotification::off(Sound::None),
SimpleNotification {
is_active: false,
sound: Sound::None
}
);
}
}