use std::collections::HashMap;
use plist::Value;
use rusqlite::{CachedStatement, Connection, Result, Row};
use crate::{
error::{plist::PlistParseError, table::TableError},
tables::{
messages::models::Service,
table::{CHAT, Cacheable, PROPERTIES, Table},
},
util::plist::{
extract_dictionary, extract_string_key, get_bool_from_dict, get_owned_string_from_dict,
plist_as_dictionary,
},
};
#[derive(Debug, PartialEq, Eq)]
pub struct Properties {
pub read_receipts_enabled: bool,
pub last_message_guid: Option<String>,
pub forced_sms: bool,
pub group_photo_guid: Option<String>,
pub has_chat_background: bool,
}
impl Properties {
pub(self) fn from_plist(plist: &Value) -> Result<Self, PlistParseError> {
Ok(Self {
read_receipts_enabled: get_bool_from_dict(plist, "EnableReadReceiptForChat")
.unwrap_or(false),
last_message_guid: get_owned_string_from_dict(plist, "lastSeenMessageGuid"),
forced_sms: get_bool_from_dict(plist, "shouldForceToSMS").unwrap_or(false),
group_photo_guid: get_owned_string_from_dict(plist, "groupPhotoGuid"),
has_chat_background: plist_as_dictionary(plist)
.and_then(|dict| extract_dictionary(dict, "backgroundProperties"))
.and_then(|dict| extract_string_key(dict, "trabar"))
.is_ok(),
})
}
}
#[derive(Debug)]
pub struct Chat {
pub rowid: i32,
pub chat_identifier: String,
pub service_name: Option<String>,
pub display_name: Option<String>,
}
impl Table for Chat {
fn from_row(row: &Row) -> Result<Chat> {
Ok(Chat {
rowid: row.get("rowid")?,
chat_identifier: row.get("chat_identifier")?,
service_name: row.get("service_name")?,
display_name: row.get("display_name").unwrap_or(None),
})
}
fn get(db: &'_ Connection) -> Result<CachedStatement<'_>, TableError> {
Ok(db.prepare_cached(&format!("SELECT * from {CHAT}"))?)
}
}
impl Cacheable for Chat {
type K = i32;
type V = Chat;
fn cache(db: &Connection) -> Result<HashMap<Self::K, Self::V>, TableError> {
let mut map = HashMap::new();
let mut statement = Chat::get(db)?;
let chats = statement.query_map([], |row| Ok(Chat::from_row(row)))?;
for chat in chats {
let result = Chat::extract(chat)?;
map.insert(result.rowid, result);
}
Ok(map)
}
}
impl Chat {
#[must_use]
pub fn name(&self) -> &str {
match self.display_name() {
Some(name) => name,
None => &self.chat_identifier,
}
}
#[must_use]
pub fn display_name(&self) -> Option<&str> {
match &self.display_name {
Some(name) => {
if !name.is_empty() {
return Some(name.as_str());
}
None
}
None => None,
}
}
#[must_use]
pub fn service(&'_ self) -> Service<'_> {
Service::from_name(self.service_name.as_deref())
}
#[must_use]
pub fn properties(&self, db: &Connection) -> Option<Properties> {
match Value::from_reader(self.get_blob(db, CHAT, PROPERTIES, self.rowid.into())?) {
Ok(plist) => Properties::from_plist(&plist).ok(),
Err(_) => None,
}
}
}
#[cfg(test)]
mod test_properties {
use plist::Value;
use std::env::current_dir;
use std::fs::File;
use crate::tables::chat::Properties;
#[test]
fn test_can_parse_properties_simple() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/ChatProp1.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: false,
last_message_guid: Some(String::from("FF0615B9-C4AF-4BD8-B9A8-1B5F9351033F")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_enable_read_receipts() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/ChatProp2.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: true,
last_message_guid: Some(String::from("678BA15C-C309-FAAC-3678-78ACE995EB54")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_third_with_summary() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/ChatProp3.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: false,
last_message_guid: Some(String::from("CEE419B6-17C7-42F7-8C2A-09A38CCA5730")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_forced_sms() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/ChatProp4.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: false,
last_message_guid: Some(String::from("87D5257D-6536-4067-A8A0-E7EF10ECBA9D")),
forced_sms: true,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_no_background() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/before_background.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: true,
last_message_guid: Some(String::from("49DA49E8-0000-0000-B59E-290294670E7D")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_added_background() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/after_background_preset.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: true,
last_message_guid: Some(String::from("49DA49E8-0000-0000-B59E-290294670E7D")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: true,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
#[test]
fn test_can_parse_properties_removed_background() {
let plist_path = current_dir()
.unwrap()
.as_path()
.join("test_data/chat_properties/after_background_removed.plist");
let plist_data = File::open(plist_path).unwrap();
let plist = Value::from_reader(plist_data).unwrap();
println!("Parsed plist: {plist:#?}");
let actual = Properties::from_plist(&plist).unwrap();
let expected = Properties {
read_receipts_enabled: true,
last_message_guid: Some(String::from("49DA49E8-0000-0000-B59E-290294670E7D")),
forced_sms: false,
group_photo_guid: None,
has_chat_background: false,
};
print!("Parsed properties: {expected:?}");
assert_eq!(actual, expected);
}
}