Implement EuphVault

This commit is contained in:
Joscha 2022-06-20 11:10:25 +02:00
parent 58c6c90055
commit dcbad0a739
7 changed files with 362 additions and 7 deletions

1
Cargo.lock generated
View file

@ -832,6 +832,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85127183a999f7db96d1a976a309eebbfb6ea3b0b400ddd8340190129de6eb7a"
dependencies = [
"bitflags",
"chrono",
"fallible-iterator",
"fallible-streaming-iterator",
"hashlink",

View file

@ -11,7 +11,7 @@ crossterm = "0.23.2"
directories = "4.0.1"
edit = "0.1.4"
parking_lot = "0.12.1"
rusqlite = "0.27.0"
rusqlite = { version = "0.27.0", features = ["chrono"] }
serde = { version = "1.0.137", features = ["derive"] }
serde_json = "1.0.81"
tokio = { version = "1.19.2", features = ["full"] }

View file

@ -4,6 +4,8 @@ use std::convert::Infallible;
use tokio::sync::{mpsc, oneshot};
pub use api::{Message, SessionView, Snowflake, Time, UserId};
enum Request {}
pub struct EuphRoom {

View file

@ -269,7 +269,7 @@ pub struct SessionView {
/// A 13-character string, usually used as aunique identifier for some type of object.
///
/// It is the base-36 encoding of an unsigned, 64-bit integer.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, PartialOrd, Ord)]
pub struct Snowflake(pub u64);
impl Serialize for Snowflake {

View file

@ -1,3 +1,4 @@
mod euph;
mod migrate;
use std::path::Path;
@ -6,9 +7,11 @@ use std::{fs, thread};
use rusqlite::Connection;
use tokio::sync::{mpsc, oneshot};
use self::euph::{EuphRequest, EuphVault};
enum Request {
Close(oneshot::Sender<()>),
Nop,
Euph(EuphRequest),
}
pub struct Vault {
@ -21,24 +24,30 @@ impl Vault {
let _ = self.tx.send(Request::Close(tx)).await;
let _ = rx.await;
}
pub fn euph(&self, room: String) -> EuphVault {
EuphVault {
tx: self.tx.clone(),
room,
}
}
}
fn run(conn: Connection, mut rx: mpsc::Receiver<Request>) -> anyhow::Result<()> {
fn run(conn: Connection, mut rx: mpsc::Receiver<Request>) {
while let Some(request) = rx.blocking_recv() {
match request {
Request::Close(tx) => {
println!("Optimizing vault");
conn.execute_batch("PRAGMA optimize")?;
let _ = conn.execute_batch("PRAGMA optimize");
// Ensure `Vault::close` exits only after the sqlite connection
// has been closed properly.
drop(conn);
drop(tx);
break;
}
Request::Nop => {}
Request::Euph(r) => r.perform(&conn),
}
}
Ok(())
}
pub fn launch(path: &Path) -> rusqlite::Result<Vault> {

329
cove-tui/src/vault/euph.rs Normal file
View file

@ -0,0 +1,329 @@
use async_trait::async_trait;
use chrono::{DateTime, Utc};
use rusqlite::{params, Connection, OptionalExtension};
use tokio::sync::{mpsc, oneshot};
use crate::euph::Snowflake;
use crate::store::{Msg, MsgStore, Path, Tree};
use super::Request;
#[derive(Debug, Clone)]
pub struct EuphMsg {
id: Snowflake,
parent: Option<Snowflake>,
time: DateTime<Utc>,
nick: String,
content: String,
}
impl Msg for EuphMsg {
type Id = Snowflake;
fn id(&self) -> Self::Id {
self.id
}
fn parent(&self) -> Option<Self::Id> {
self.parent
}
fn time(&self) -> DateTime<Utc> {
self.time
}
fn nick(&self) -> String {
self.nick.clone()
}
fn content(&self) -> String {
self.content.clone()
}
}
impl From<EuphRequest> for Request {
fn from(r: EuphRequest) -> Self {
Self::Euph(r)
}
}
pub struct EuphVault {
pub(super) tx: mpsc::Sender<Request>,
pub(super) room: String,
}
#[async_trait]
impl MsgStore<EuphMsg> for EuphVault {
async fn path(&self, id: &Snowflake) -> Path<Snowflake> {
// 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()).await;
rx.await.unwrap()
}
async fn tree(&self, root: &Snowflake) -> Tree<EuphMsg> {
// 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()).await;
rx.await.unwrap()
}
async fn prev_tree(&self, root: &Snowflake) -> Option<Snowflake> {
// 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()).await;
rx.await.unwrap()
}
async fn next_tree(&self, root: &Snowflake) -> Option<Snowflake> {
// 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()).await;
rx.await.unwrap()
}
async fn first_tree(&self) -> Option<Snowflake> {
// TODO vault::Error
let (tx, rx) = oneshot::channel();
let request = EuphRequest::FirstTree {
room: self.room.clone(),
result: tx,
};
let _ = self.tx.send(request.into()).await;
rx.await.unwrap()
}
async fn last_tree(&self) -> Option<Snowflake> {
// TODO vault::Error
let (tx, rx) = oneshot::channel();
let request = EuphRequest::LastTree {
room: self.room.clone(),
result: tx,
};
let _ = self.tx.send(request.into()).await;
rx.await.unwrap()
}
}
pub(super) enum EuphRequest {
Path {
room: String,
id: Snowflake,
result: oneshot::Sender<Path<Snowflake>>,
},
Tree {
room: String,
root: Snowflake,
result: oneshot::Sender<Tree<EuphMsg>>,
},
PrevTree {
room: String,
root: Snowflake,
result: oneshot::Sender<Option<Snowflake>>,
},
NextTree {
room: String,
root: Snowflake,
result: oneshot::Sender<Option<Snowflake>>,
},
FirstTree {
room: String,
result: oneshot::Sender<Option<Snowflake>>,
},
LastTree {
room: String,
result: oneshot::Sender<Option<Snowflake>>,
},
}
impl EuphRequest {
pub(super) fn perform(self, conn: &Connection) {
let _ = match self {
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),
};
}
fn path(
conn: &Connection,
room: String,
id: Snowflake,
result: oneshot::Sender<Path<Snowflake>>,
) -> rusqlite::Result<()> {
let path = conn
.prepare(
"
WITH RECURSIVE path (room, id) = (
VALUES (?, ?)
UNION
SELECT (room, parent)
FROM euph_msgs
JOIN path USING (room, id)
)
SELECT id
FROM path
ORDER BY id ASC
",
)?
.query_map(params![room, id.0], |row| row.get(0).map(Snowflake))?
.collect::<rusqlite::Result<_>>()?;
let path = Path::new(path);
let _ = result.send(path);
Ok(())
}
fn tree(
conn: &Connection,
room: String,
root: Snowflake,
result: oneshot::Sender<Tree<EuphMsg>>,
) -> rusqlite::Result<()> {
let msgs = conn
.prepare(
"
WITH RECURSIVE tree (room, id) = (
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_msg
JOIN tree USING (room, id)
ORDER BY id ASC
",
)?
.query_map(params![room, root.0], |row| {
Ok(EuphMsg {
id: Snowflake(row.get(0)?),
parent: row.get::<_, Option<u64>>(1)?.map(Snowflake),
time: row.get(2)?,
nick: row.get(3)?,
content: row.get(4)?,
})
})?
.collect::<rusqlite::Result<_>>()?;
let tree = Tree::new(root, msgs);
let _ = result.send(tree);
Ok(())
}
fn prev_tree(
conn: &Connection,
room: String,
root: Snowflake,
result: oneshot::Sender<Option<Snowflake>>,
) -> 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.0], |row| row.get(0).map(Snowflake))
.optional()?;
let _ = result.send(tree);
Ok(())
}
fn next_tree(
conn: &Connection,
room: String,
root: Snowflake,
result: oneshot::Sender<Option<Snowflake>>,
) -> 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.0], |row| row.get(0).map(Snowflake))
.optional()?;
let _ = result.send(tree);
Ok(())
}
fn first_tree(
conn: &Connection,
room: String,
result: oneshot::Sender<Option<Snowflake>>,
) -> 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).map(Snowflake))
.optional()?;
let _ = result.send(tree);
Ok(())
}
fn last_tree(
conn: &Connection,
room: String,
result: oneshot::Sender<Option<Snowflake>>,
) -> 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).map(Snowflake))
.optional()?;
let _ = result.send(tree);
Ok(())
}
}

View file

@ -57,6 +57,20 @@ fn m1(tx: &mut Transaction) -> rusqlite::Result<()> {
FOREIGN KEY (room, start) REFERENCES euph_msgs (room, start),
FOREIGN KEY (room, end) REFERENCES euph_msgs (room, end)
) STRICT;
CREATE VIEW euph_trees (room, id) AS
SELECT room, id
FROM euph_msgs
WHERE parent IS NULL
UNION
(
SELECT room, parent
FROM euph_msgs
WHERE parent IS NOT NULL
EXCEPT
SELECT room, id
FROM euph_msgs
)
",
)
}