Dissolve workspace
This commit is contained in:
parent
1cc7dd8920
commit
e601476d02
29 changed files with 24 additions and 29 deletions
329
src/vault/euph.rs
Normal file
329
src/vault/euph.rs
Normal 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::api::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(())
|
||||
}
|
||||
}
|
||||
76
src/vault/migrate.rs
Normal file
76
src/vault/migrate.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
use rusqlite::{Connection, Transaction};
|
||||
|
||||
pub fn migrate(conn: &mut Connection) -> rusqlite::Result<()> {
|
||||
let mut tx = conn.transaction()?;
|
||||
|
||||
let user_version: usize =
|
||||
tx.query_row("SELECT * FROM pragma_user_version", [], |r| r.get(0))?;
|
||||
|
||||
let total = MIGRATIONS.len();
|
||||
for (i, migration) in MIGRATIONS.iter().enumerate().skip(user_version) {
|
||||
println!("Migrating vault from {} to {} (out of {})", i, i + 1, total);
|
||||
migration(&mut tx)?;
|
||||
}
|
||||
|
||||
tx.pragma_update(None, "user_version", total)?;
|
||||
tx.commit()
|
||||
}
|
||||
|
||||
const MIGRATIONS: [fn(&mut Transaction) -> rusqlite::Result<()>; 1] = [m1];
|
||||
|
||||
fn m1(tx: &mut Transaction) -> rusqlite::Result<()> {
|
||||
tx.execute_batch(
|
||||
"
|
||||
CREATE TABLE euph_msgs (
|
||||
-- Message
|
||||
room TEXT NOT NULL,
|
||||
id INT NOT NULL,
|
||||
parent INT,
|
||||
previous_edit_id INT,
|
||||
time INT NOT NULL,
|
||||
content TEXT NOT NULL,
|
||||
encryption_key_id TEXT,
|
||||
edited INT,
|
||||
deleted INT,
|
||||
truncated INT NOT NULL,
|
||||
|
||||
-- SessionView
|
||||
user_id TEXT NOT NULL,
|
||||
name TEXT
|
||||
server_id TEXT NOT NULL,
|
||||
server_era TEXT NOT NULL,
|
||||
session_id TEXT NOT NULL,
|
||||
is_staff INT NOT NULL,
|
||||
is_manager INT NOT NULL,
|
||||
client_address TEXT,
|
||||
real_client_address TEXT,
|
||||
|
||||
PRIMARY KEY (room, id)
|
||||
) STRICT;
|
||||
|
||||
CREATE TABLE euph_spans (
|
||||
room TEXT NOT NULL,
|
||||
start INT,
|
||||
end INT,
|
||||
|
||||
PRIMARY KEY (room, start, end),
|
||||
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
|
||||
)
|
||||
",
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue