use std::mem; use async_trait::async_trait; use chrono::{DateTime, TimeZone, Utc}; use rusqlite::types::{FromSql, FromSqlError, ToSqlOutput, Value, ValueRef}; use rusqlite::{named_params, params, Connection, OptionalExtension, ToSql, Transaction}; use tokio::sync::{mpsc, oneshot}; use crate::euph::api::{Message, Snowflake, Time}; use crate::store::{Msg, MsgStore, Path, Tree}; use super::{Request, Vault}; impl ToSql for Snowflake { fn to_sql(&self) -> rusqlite::Result> { self.0.to_sql() } } impl FromSql for Snowflake { fn column_result(value: ValueRef<'_>) -> Result { u64::column_result(value).map(Self) } } impl ToSql for Time { fn to_sql(&self) -> rusqlite::Result> { let timestamp = self.0.timestamp(); Ok(ToSqlOutput::Owned(Value::Integer(timestamp))) } } impl FromSql for Time { fn column_result(value: ValueRef<'_>) -> rusqlite::types::FromSqlResult { let timestamp = i64::column_result(value)?; Ok(Self(Utc.timestamp(timestamp, 0))) } } #[derive(Debug, Clone)] pub struct EuphMsg { id: Snowflake, parent: Option, time: Time, nick: String, content: String, } impl Msg for EuphMsg { type Id = Snowflake; fn id(&self) -> Self::Id { self.id } fn parent(&self) -> Option { self.parent } fn time(&self) -> DateTime { self.time.0 } fn nick(&self) -> String { self.nick.clone() } fn content(&self) -> String { self.content.trim().to_string() } } impl From for Request { fn from(r: EuphRequest) -> Self { Self::Euph(r) } } impl Vault { pub async fn euph_rooms(&self) -> Vec { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::Rooms { result: tx }; let _ = self.tx.send(request.into()); rx.await.unwrap() } } #[derive(Debug, Clone)] pub struct EuphVault { pub(super) tx: mpsc::UnboundedSender, pub(super) room: String, } impl EuphVault { pub fn join(&self) { let request = EuphRequest::Join { room: self.room.clone(), }; let _ = self.tx.send(request.into()); } pub fn add_message(&self, msg: Message, prev_msg: Option) { let request = EuphRequest::AddMsg { room: self.room.clone(), msg, prev_msg, }; let _ = self.tx.send(request.into()); } pub fn add_messages(&self, msgs: Vec, next_msg: Option) { let request = EuphRequest::AddMsgs { room: self.room.clone(), msgs, next_msg, }; let _ = self.tx.send(request.into()); } pub async fn last_span(&self) -> Option<(Option, Option)> { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::LastSpan { room: self.room.clone(), result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } } #[async_trait] impl MsgStore for EuphVault { async fn path(&self, id: &Snowflake) -> Path { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::Path { room: self.room.clone(), id: *id, result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } async fn tree(&self, root: &Snowflake) -> Tree { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::Tree { room: self.room.clone(), root: *root, result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } async fn prev_tree(&self, root: &Snowflake) -> Option { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::PrevTree { room: self.room.clone(), root: *root, result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } async fn next_tree(&self, root: &Snowflake) -> Option { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::NextTree { room: self.room.clone(), root: *root, result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } async fn first_tree(&self) -> Option { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::FirstTree { room: self.room.clone(), result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } async fn last_tree(&self) -> Option { // TODO vault::Error let (tx, rx) = oneshot::channel(); let request = EuphRequest::LastTree { room: self.room.clone(), result: tx, }; let _ = self.tx.send(request.into()); rx.await.unwrap() } } pub(super) enum EuphRequest { Rooms { result: oneshot::Sender>, }, Join { room: String, }, AddMsg { room: String, msg: Message, prev_msg: Option, }, AddMsgs { room: String, msgs: Vec, next_msg: Option, }, LastSpan { room: String, result: oneshot::Sender, Option)>>, }, Path { room: String, id: Snowflake, result: oneshot::Sender>, }, Tree { room: String, root: Snowflake, result: oneshot::Sender>, }, PrevTree { room: String, root: Snowflake, result: oneshot::Sender>, }, NextTree { room: String, root: Snowflake, result: oneshot::Sender>, }, FirstTree { room: String, result: oneshot::Sender>, }, LastTree { room: String, result: oneshot::Sender>, }, } impl EuphRequest { pub(super) fn perform(self, conn: &mut Connection) { let result = match self { EuphRequest::Rooms { result } => Self::rooms(conn, result), EuphRequest::Join { room } => Self::join(conn, room), EuphRequest::AddMsg { room, msg, prev_msg, } => Self::add_msg(conn, room, msg, prev_msg), EuphRequest::AddMsgs { room, msgs, next_msg, } => Self::add_msgs(conn, room, msgs, next_msg), EuphRequest::LastSpan { room, result } => Self::last_span(conn, room, result), EuphRequest::Path { room, id, result } => Self::path(conn, room, id, result), EuphRequest::Tree { room, root, result } => Self::tree(conn, room, root, result), EuphRequest::PrevTree { room, root, result } => { Self::prev_tree(conn, room, root, result) } EuphRequest::NextTree { room, root, result } => { Self::next_tree(conn, room, root, result) } EuphRequest::FirstTree { room, result } => Self::first_tree(conn, room, result), EuphRequest::LastTree { room, result } => Self::last_tree(conn, room, result), }; if let Err(e) = result { // If an error occurs here, the rest of the UI will likely panic and // crash soon. By printing this to stderr instead of logging it, we // can filter it out and read it later. // TODO Better vault error handling eprintln!("{e}"); } } fn rooms(conn: &mut Connection, result: oneshot::Sender>) -> rusqlite::Result<()> { let rooms = conn .prepare( " SELECT room FROM euph_rooms ", )? .query_map([], |row| row.get(0))? .collect::>()?; let _ = result.send(rooms); Ok(()) } fn join(conn: &mut Connection, room: String) -> rusqlite::Result<()> { conn.execute( " INSERT INTO euph_rooms (room) VALUES (?) ", [room], )?; Ok(()) } fn insert_msgs(tx: &Transaction, room: &str, msgs: Vec) -> rusqlite::Result<()> { let mut insert_msg = tx.prepare( " INSERT OR REPLACE INTO euph_msgs ( room, id, parent, previous_edit_id, time, content, encryption_key_id, edited, deleted, truncated, user_id, name, server_id, server_era, session_id, is_staff, is_manager, client_address, real_client_address ) VALUES ( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? ) " )?; let mut delete_trees = tx.prepare( " DELETE FROM euph_trees WHERE room = ? AND id = ? ", )?; let mut insert_trees = tx.prepare( " INSERT OR IGNORE INTO euph_trees (room, id) SELECT * FROM (VALUES (:room, :id)) WHERE NOT EXISTS( SELECT * FROM euph_msgs WHERE room = :room AND id = :id AND parent IS NOT NULL ) ", )?; for msg in msgs { insert_msg.execute(params![ room, msg.id, msg.parent, msg.previous_edit_id, msg.time, msg.content, msg.encryption_key_id, msg.edited, msg.deleted, msg.truncated, msg.sender.id.0, msg.sender.name, msg.sender.server_id, msg.sender.server_era, msg.sender.session_id, msg.sender.is_staff, msg.sender.is_manager, msg.sender.client_address, msg.sender.real_client_address, ])?; if let Some(parent) = msg.parent { delete_trees.execute(params![room, msg.id])?; insert_trees.execute(named_params! {":room": room,":id": parent})?; } else { insert_trees.execute(named_params! {":room": room,":id": msg.id})?; } } Ok(()) } fn add_span( tx: &Transaction, room: &str, start: Option, end: Option, ) -> rusqlite::Result<()> { // Retrieve all spans for the room let mut spans = tx .prepare( " SELECT start, end FROM euph_spans WHERE room = ? ", )? .query_map([room], |row| { let start = row.get::<_, Option>(0)?; let end = row.get::<_, Option>(1)?; Ok((start, end)) })? .collect::, _>>()?; // Add new span and sort spans lexicographically spans.push((start, end)); spans.sort_unstable(); // Combine overlapping spans (including newly added span) let mut cur_span: Option<(Option, Option)> = None; let mut result = vec![]; for mut span in spans { if let Some(cur_span) = &mut cur_span { if span.0 <= cur_span.1 { // Since spans are sorted lexicographically, we know that // cur_span.0 <= span.0, which means that span starts inside // of cur_span. cur_span.1 = cur_span.1.max(span.1); } else { // Since span doesn't overlap cur_span, we know that no // later span will overlap cur_span either. The size of // cur_span is thus final. mem::swap(cur_span, &mut span); result.push(span); } } else { cur_span = Some(span); } } if let Some(cur_span) = cur_span { result.push(cur_span); } // Delete all spans for the room tx.execute( " DELETE FROM euph_spans WHERE room = ? ", [room], )?; // Re-insert combined spans for the room let mut stmt = tx.prepare( " INSERT INTO euph_spans (room, start, end) VALUES (?, ?, ?) ", )?; for (start, end) in result { stmt.execute(params![room, start, end])?; } Ok(()) } fn add_msg( conn: &mut Connection, room: String, msg: Message, prev_msg: Option, ) -> rusqlite::Result<()> { let tx = conn.transaction()?; let end = msg.id; Self::insert_msgs(&tx, &room, vec![msg])?; Self::add_span(&tx, &room, prev_msg, Some(end))?; tx.commit()?; Ok(()) } fn add_msgs( conn: &mut Connection, room: String, msgs: Vec, next_msg_id: Option, ) -> rusqlite::Result<()> { let tx = conn.transaction()?; if msgs.is_empty() { Self::add_span(&tx, &room, None, next_msg_id)?; } else { let first_msg_id = msgs.first().unwrap().id; let last_msg_id = msgs.last().unwrap().id; Self::insert_msgs(&tx, &room, msgs)?; let end = next_msg_id.unwrap_or(last_msg_id); Self::add_span(&tx, &room, Some(first_msg_id), Some(end))?; } tx.commit()?; Ok(()) } fn last_span( conn: &Connection, room: String, result: oneshot::Sender, Option)>>, ) -> rusqlite::Result<()> { let span = conn .prepare( " SELECT start, end FROM euph_spans WHERE room = ? ORDER BY start DESC LIMIT 1 ", )? .query_row([room], |row| Ok((row.get(0)?, row.get(1)?))) .optional()?; let _ = result.send(span); Ok(()) } fn path( conn: &Connection, room: String, id: Snowflake, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let path = conn .prepare( " WITH RECURSIVE path (room, id) AS ( VALUES (?, ?) UNION SELECT room, parent FROM euph_msgs JOIN path USING (room, id) ) SELECT id FROM path WHERE id IS NOT NULL ORDER BY id ASC ", )? .query_map(params![room, id], |row| row.get(0))? .collect::>()?; let path = Path::new(path); let _ = result.send(path); Ok(()) } fn tree( conn: &Connection, room: String, root: Snowflake, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let msgs = conn .prepare( " WITH RECURSIVE tree (room, id) AS ( VALUES (?, ?) UNION SELECT euph_msgs.room, euph_msgs.id FROM euph_msgs JOIN tree ON tree.room = euph_msgs.room AND tree.id = euph_msgs.parent ) SELECT id, parent, time, name, content FROM euph_msgs JOIN tree USING (room, id) ORDER BY id ASC ", )? .query_map(params![room, root], |row| { Ok(EuphMsg { id: row.get(0)?, parent: row.get(1)?, time: row.get(2)?, nick: row.get(3)?, content: row.get(4)?, }) })? .collect::>()?; let tree = Tree::new(root, msgs); let _ = result.send(tree); Ok(()) } fn prev_tree( conn: &Connection, room: String, root: Snowflake, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let tree = conn .prepare( " SELECT id FROM euph_trees WHERE room = ? AND id < ? ORDER BY id DESC LIMIT 1 ", )? .query_row(params![room, root], |row| row.get(0)) .optional()?; let _ = result.send(tree); Ok(()) } fn next_tree( conn: &Connection, room: String, root: Snowflake, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let tree = conn .prepare( " SELECT id FROM euph_trees WHERE room = ? AND id > ? ORDER BY id ASC LIMIT 1 ", )? .query_row(params![room, root], |row| row.get(0)) .optional()?; let _ = result.send(tree); Ok(()) } fn first_tree( conn: &Connection, room: String, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let tree = conn .prepare( " SELECT id FROM euph_trees WHERE room = ? ORDER BY id ASC LIMIT 1 ", )? .query_row([room], |row| row.get(0)) .optional()?; let _ = result.send(tree); Ok(()) } fn last_tree( conn: &Connection, room: String, result: oneshot::Sender>, ) -> rusqlite::Result<()> { let tree = conn .prepare( " SELECT id FROM euph_trees WHERE room = ? ORDER BY id DESC LIMIT 1 ", )? .query_row([room], |row| row.get(0)) .optional()?; let _ = result.send(tree); Ok(()) } }