291 lines
7.5 KiB
Rust
291 lines
7.5 KiB
Rust
use crate::log;
|
|
use crate::util::file_util::get_directory;
|
|
use json::{JsonValue, array, object};
|
|
use rusqlite::{Connection, params};
|
|
use std::io;
|
|
|
|
#[derive(PartialEq, Debug, Clone)]
|
|
pub enum MessageState {
|
|
Read,
|
|
Received,
|
|
Sent,
|
|
Sending,
|
|
}
|
|
|
|
impl MessageState {
|
|
pub fn as_str(&self) -> &'static str {
|
|
match self {
|
|
MessageState::Read => "read",
|
|
MessageState::Received => "received",
|
|
MessageState::Sent => "sent",
|
|
MessageState::Sending => "sending",
|
|
}
|
|
}
|
|
|
|
pub fn from_str(value: &str) -> Self {
|
|
match value.to_lowercase().as_str() {
|
|
"read" => MessageState::Read,
|
|
"received" => MessageState::Received,
|
|
"sent" => MessageState::Sent,
|
|
_ => MessageState::Sending,
|
|
}
|
|
}
|
|
|
|
pub fn upgrade(self, other: Self) -> Self {
|
|
if other == Self::Read || self == Self::Read {
|
|
Self::Read
|
|
} else if other == Self::Received || self == Self::Received {
|
|
Self::Received
|
|
} else if other == Self::Sent || self == Self::Sent {
|
|
Self::Sent
|
|
} else {
|
|
Self::Sending
|
|
}
|
|
}
|
|
}
|
|
|
|
fn db_path() -> String {
|
|
format!("{}/messages.sqlite3", get_directory())
|
|
}
|
|
|
|
fn open_db() -> rusqlite::Result<Connection> {
|
|
let conn = Connection::open(db_path())?;
|
|
conn.execute_batch(
|
|
r#"
|
|
PRAGMA journal_mode = WAL;
|
|
PRAGMA synchronous = NORMAL;
|
|
|
|
CREATE TABLE IF NOT EXISTS messages (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
storage_owner INTEGER NOT NULL,
|
|
external_user INTEGER NOT NULL,
|
|
message_time INTEGER NOT NULL,
|
|
content TEXT NOT NULL,
|
|
sent_by_self INTEGER NOT NULL,
|
|
message_state TEXT NOT NULL
|
|
);
|
|
|
|
CREATE INDEX IF NOT EXISTS idx_messages_lookup
|
|
ON messages (storage_owner, external_user, message_time DESC);
|
|
"#,
|
|
)?;
|
|
Ok(conn)
|
|
}
|
|
|
|
pub fn add_message(
|
|
send_time: u128,
|
|
storage_owner_is_sender: bool,
|
|
storage_owner: i64,
|
|
external_user: i64,
|
|
message: &str,
|
|
) {
|
|
let message_time = match i64::try_from(send_time) {
|
|
Ok(v) => v,
|
|
Err(_) => {
|
|
log!("Failed to store message: send_time out of range for i64 ({send_time})");
|
|
return;
|
|
}
|
|
};
|
|
|
|
let conn = match open_db() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
log!("Failed to open sqlite db for add_message: {}", e);
|
|
return;
|
|
}
|
|
};
|
|
|
|
if let Err(e) = conn.execute(
|
|
r#"
|
|
INSERT INTO messages (
|
|
storage_owner,
|
|
external_user,
|
|
message_time,
|
|
content,
|
|
sent_by_self,
|
|
message_state
|
|
) VALUES (?1, ?2, ?3, ?4, ?5, ?6)
|
|
"#,
|
|
params![
|
|
storage_owner,
|
|
external_user,
|
|
message_time,
|
|
message,
|
|
if storage_owner_is_sender {
|
|
1_i64
|
|
} else {
|
|
0_i64
|
|
},
|
|
MessageState::Sending.as_str(),
|
|
],
|
|
) {
|
|
log!("Failed to insert message into sqlite: {}", e);
|
|
}
|
|
}
|
|
|
|
pub fn change_message_state(
|
|
timestamp: i64,
|
|
storage_owner: i64,
|
|
external_user: i64,
|
|
new_state: MessageState,
|
|
) -> io::Result<()> {
|
|
let conn = open_db().map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
|
|
|
let current: Option<String> = match conn.query_row(
|
|
r#"
|
|
SELECT message_state
|
|
FROM messages
|
|
WHERE storage_owner = ?1
|
|
AND external_user = ?2
|
|
AND message_time = ?3
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
"#,
|
|
params![storage_owner, external_user, timestamp],
|
|
|row| row.get(0),
|
|
) {
|
|
Ok(state) => Some(state),
|
|
Err(rusqlite::Error::QueryReturnedNoRows) => None,
|
|
Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())),
|
|
};
|
|
|
|
let Some(current_state_raw) = current else {
|
|
return Ok(());
|
|
};
|
|
|
|
let upgraded = MessageState::from_str(¤t_state_raw)
|
|
.upgrade(new_state)
|
|
.as_str()
|
|
.to_string();
|
|
|
|
conn.execute(
|
|
r#"
|
|
UPDATE messages
|
|
SET message_state = ?1
|
|
WHERE id = (
|
|
SELECT id
|
|
FROM messages
|
|
WHERE storage_owner = ?2
|
|
AND external_user = ?3
|
|
AND message_time = ?4
|
|
ORDER BY id DESC
|
|
LIMIT 1
|
|
)
|
|
"#,
|
|
params![upgraded, storage_owner, external_user, timestamp],
|
|
)
|
|
.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub fn get_messages(
|
|
storage_owner: i64,
|
|
external_user: i64,
|
|
loaded_messages: i64,
|
|
amount: i64,
|
|
) -> JsonValue {
|
|
let mut messages = array![];
|
|
|
|
if amount <= 0 || loaded_messages < 0 {
|
|
return messages;
|
|
}
|
|
|
|
let conn = match open_db() {
|
|
Ok(c) => c,
|
|
Err(e) => {
|
|
log!("Failed to open sqlite db for get_messages: {}", e);
|
|
return messages;
|
|
}
|
|
};
|
|
|
|
let mut stmt = match conn.prepare(
|
|
r#"
|
|
SELECT
|
|
message_time,
|
|
content,
|
|
sent_by_self,
|
|
message_state
|
|
FROM messages
|
|
WHERE storage_owner = ?1
|
|
AND external_user = ?2
|
|
ORDER BY message_time DESC, id DESC
|
|
LIMIT ?3 OFFSET ?4
|
|
"#,
|
|
) {
|
|
Ok(s) => s,
|
|
Err(e) => {
|
|
log!("Failed to prepare get_messages query: {}", e);
|
|
return messages;
|
|
}
|
|
};
|
|
|
|
let rows = stmt.query_map(
|
|
params![storage_owner, external_user, amount, loaded_messages],
|
|
|row| {
|
|
let message_time: i64 = row.get(0)?;
|
|
let content: String = row.get(1)?;
|
|
let sent_by_self: i64 = row.get(2)?;
|
|
let message_state: String = row.get(3)?;
|
|
Ok((message_time, content, sent_by_self, message_state))
|
|
},
|
|
);
|
|
|
|
let Ok(rows) = rows else {
|
|
if let Err(e) = rows {
|
|
log!("Failed to query messages: {}", e);
|
|
}
|
|
return messages;
|
|
};
|
|
|
|
for row in rows {
|
|
match row {
|
|
Ok((message_time, content, sent_by_self, message_state)) => {
|
|
let msg = object! {
|
|
"message_time" => message_time,
|
|
"content" => content,
|
|
"sent_by_self" => (sent_by_self != 0),
|
|
"message_state" => message_state
|
|
};
|
|
|
|
if let Err(e) = messages.push(msg) {
|
|
log!("Failed to append message to output array: {}", e);
|
|
}
|
|
}
|
|
Err(e) => {
|
|
log!("Failed to read row from sqlite: {}", e);
|
|
}
|
|
}
|
|
}
|
|
|
|
messages
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::MessageState;
|
|
|
|
#[test]
|
|
fn upgrade_prefers_highest_state() {
|
|
assert_eq!(
|
|
MessageState::Sending.upgrade(MessageState::Sent),
|
|
MessageState::Sent
|
|
);
|
|
assert_eq!(
|
|
MessageState::Sent.upgrade(MessageState::Received),
|
|
MessageState::Received
|
|
);
|
|
assert_eq!(
|
|
MessageState::Received.upgrade(MessageState::Read),
|
|
MessageState::Read
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn from_str_is_case_insensitive() {
|
|
assert_eq!(MessageState::from_str("READ"), MessageState::Read);
|
|
assert_eq!(MessageState::from_str("received"), MessageState::Received);
|
|
assert_eq!(MessageState::from_str("Sent"), MessageState::Sent);
|
|
assert_eq!(MessageState::from_str("unknown"), MessageState::Sending);
|
|
}
|
|
}
|