use crate::error::Result;
use crate::util::text::{decode_text, encode_text, TextEncoding};
use byteorder::ReadBytesExt;
use std::hash::{Hash, Hasher};
#[derive(Clone, Debug, Eq)]
pub struct Popularimeter {
pub email: String,
pub rating: u8,
pub counter: u64,
}
impl Popularimeter {
pub fn as_bytes(&self) -> Vec<u8> {
let mut content = Vec::with_capacity(self.email.len() + 9);
content.extend(encode_text(self.email.as_str(), TextEncoding::Latin1, true));
content.push(self.rating);
if let Ok(counter) = u32::try_from(self.counter) {
content.extend(counter.to_be_bytes())
} else {
let counter_bytes = self.counter.to_be_bytes();
let i = counter_bytes.iter().position(|b| *b != 0).unwrap_or(4);
content.extend(&counter_bytes[i..]);
}
content
}
pub fn from_bytes(mut bytes: &[u8]) -> Result<Self> {
let content = &mut bytes;
let email = decode_text(content, TextEncoding::Latin1, true)?;
let rating = content.read_u8()?;
let counter;
let remaining_size = content.len();
if remaining_size > 8 {
counter = u64::MAX;
} else {
let mut counter_bytes = [0; 8];
let counter_start_pos = 8 - remaining_size;
counter_bytes[counter_start_pos..].copy_from_slice(content);
counter = u64::from_be_bytes(counter_bytes);
}
Ok(Self {
email: email.unwrap_or_default(),
rating,
counter,
})
}
}
impl PartialEq for Popularimeter {
fn eq(&self, other: &Self) -> bool {
self.email == other.email
}
}
impl Hash for Popularimeter {
fn hash<H: Hasher>(&self, state: &mut H) {
self.email.hash(state);
}
}
#[cfg(test)]
mod tests {
use crate::id3::v2::items::popularimeter::Popularimeter;
fn test_popm(popm: &Popularimeter) {
let email = popm.email.clone();
let rating = popm.rating;
let counter = popm.counter;
let popm_bytes = popm.as_bytes();
assert_eq!(&popm_bytes[..email.len()], email.as_bytes());
assert_eq!(popm_bytes[email.len()], 0);
assert_eq!(popm_bytes[email.len() + 1], rating);
let counter_len = if u32::try_from(counter).is_ok() {
4
} else {
let counter_bytes = counter.to_be_bytes();
let i = counter_bytes.iter().position(|b| *b != 0).unwrap_or(4);
counter_bytes.len() - i
};
assert_eq!(popm_bytes[email.len() + 2..].len(), counter_len);
}
#[test]
fn write_popm() {
let popm_u32_boundary = Popularimeter {
email: String::from("foo@bar.com"),
rating: 255,
counter: u64::from(u32::MAX),
};
let popm_u40 = Popularimeter {
email: String::from("baz@qux.com"),
rating: 196,
counter: u64::from(u32::MAX) + 1,
};
test_popm(&popm_u32_boundary);
test_popm(&popm_u40);
}
}