make temporary gui hack use sqlite store

This commit is contained in:
tezlm 2024-02-12 23:17:35 -08:00
parent c3c41a6092
commit 4d5cd581da
Signed by: tezlm
GPG key ID: 649733FCD94AFBBA
8 changed files with 180 additions and 83 deletions

15
Cargo.lock generated
View file

@ -445,18 +445,18 @@ dependencies = [
[[package]]
name = "clipboard_wayland"
version = "0.2.1"
version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8134163bd07c47ae3cc29babc42c255fdb315facc790950ae2d0e561ea6f2ec0"
checksum = "003f886bc4e2987729d10c1db3424e7f80809f3fc22dbc16c685738887cb37b8"
dependencies = [
"smithay-clipboard",
]
[[package]]
name = "clipboard_x11"
version = "0.4.1"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5cf45b436634fee64c6d3981639b46a87eeea3c64e422643273fcefd1baef56c"
checksum = "4274ea815e013e0f9f04a2633423e14194e408a0576c943ce3d14ca56c50031c"
dependencies = [
"thiserror",
"x11rb 0.13.0",
@ -1121,8 +1121,11 @@ dependencies = [
name = "gui"
version = "0.1.0"
dependencies = [
"anyhow",
"dag-resolve",
"iced",
"rusqlite",
"serde_json",
]
[[package]]
@ -3175,9 +3178,9 @@ dependencies = [
[[package]]
name = "web-sys"
version = "0.3.68"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96565907687f7aceb35bc5fc03770a8a0471d82e479f25832f54a0e3f4b28446"
checksum = "58cd2333b6e0be7a39605f0e255892fd7418a682d8da8fe042fe25128794d2ed"
dependencies = [
"js-sys",
"wasm-bindgen",

View file

@ -214,7 +214,7 @@ fn startup(path: impl AsRef<Path>, create: Option<&str>) -> Result<Opened> {
}
}
fn sync_state<R: Resolver + Debug>(mut from: State<R>, mut to: State<R>) -> Result<()> {
fn sync_state<R: Resolver + Debug>(from: &mut State<R>, to: &mut State<R>) -> Result<()> {
if from.room.get_root() != to.room.get_root() {
panic!("cannot sync events from two different rooms");
}
@ -224,18 +224,16 @@ fn sync_state<R: Resolver + Debug>(mut from: State<R>, mut to: State<R>) -> Resu
for event in &to.room.events {
from.append_event(event.clone())?;
}
dbg!(
from.room.resolve_state(*from.store),
to.room.resolve_state(*to.store)
);
dbg!(from.room);
from.room.resolve_state(&mut *from.store);
to.room.resolve_state(&mut *to.store);
dbg!(&from.store, &to.store);
Ok(())
}
fn send_event<R: Resolver + Debug>(mut state: State<R>, data: &str) -> Result<()> {
fn send_event<R: Resolver + Debug>(state: &mut State<R>, data: &str) -> Result<()> {
state.create_event(dbg!(serde_json::from_str(dbg!(data))?))?;
dbg!(&state.room);
dbg!(&state.room.resolve_state(*state.store));
dbg!(&state.room.resolve_state(&mut *state.store));
Ok(())
}
@ -272,19 +270,19 @@ fn main() -> Result<()> {
};
match (cli, opened) {
(Cli::Init { .. }, _) => {},
(Cli::Send { repo: _, data }, Opened::Kv(state)) => send_event(state, &data)?,
(Cli::Send { repo: _, data }, Opened::Forum(state)) => send_event(state, &data)?,
(Cli::Send { repo: _, data }, Opened::Kv(mut state)) => send_event(&mut state, &data)?,
(Cli::Send { repo: _, data }, Opened::Forum(mut state)) => send_event(&mut state, &data)?,
(Cli::Info { repo: _ }, Opened::Kv(state)) => print_info(state)?,
(Cli::Info { repo: _ }, Opened::Forum(state)) => print_info(state)?,
(Cli::Sync { repo: _, other }, Opened::Kv(our_state)) => {
(Cli::Sync { repo: _, other }, Opened::Kv(mut our_state)) => {
match startup(other, None)? {
Opened::Kv(their_state) => sync_state(our_state, their_state)?,
Opened::Kv(mut their_state) => sync_state(&mut our_state, &mut their_state)?,
_ => panic!("cannot sync different room types"),
}
},
(Cli::Sync { repo: _, other }, Opened::Forum(our_state)) => {
(Cli::Sync { repo: _, other }, Opened::Forum(mut our_state)) => {
match startup(other, None)? {
Opened::Forum(their_state) => sync_state(our_state, their_state)?,
Opened::Forum(mut their_state) => sync_state(&mut our_state, &mut their_state)?,
_ => panic!("cannot sync different room types"),
}
},

View file

@ -6,5 +6,8 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.79"
dag-resolve = { version = "0.1.0", path = "../lib" }
iced = { version = "0.10.0", features = ["debug"] }
rusqlite = { version = "0.30.0", features = ["bundled"] }
serde_json = "1.0.113"

View file

@ -1,13 +1,13 @@
use std::sync::Mutex;
use std::{path::Path, rc::Rc, sync::Mutex};
use anyhow::Result;
use dag_resolve::{
actor::{ActorId, ActorSecret},
event::{CoreContent, HashType, SignatureType},
proto::table::{State, Table},
event::{CoreContent, Event, HashType, SignatureType},
proto::table::{State as _, Table as _},
resolver::Resolver,
resolvers::kv::{KVEventContent, KVResolver},
room::Room,
store::memory::MemoryStore,
};
use iced::{
keyboard::KeyCode,
@ -16,13 +16,11 @@ use iced::{
};
struct Flags {
room: Room<KVResolver>,
secret: ActorSecret,
state: State<KVResolver>,
}
struct Counter {
room: Mutex<Room<KVResolver>>,
secret: ActorSecret,
struct Main {
state: Mutex<State<KVResolver>>,
key: String,
value: String,
key_id: text_input::Id,
@ -39,17 +37,16 @@ enum Message {
FocusPrev,
}
impl Application for Counter {
impl Application for Main {
type Executor = iced::executor::Default;
type Message = Message;
type Theme = iced::Theme;
type Flags = Flags;
fn new(flags: Self::Flags) -> (Self, Command<Self::Message>) {
let room = Mutex::new(flags.room);
let state = Mutex::new(flags.state);
let state = Self {
room,
secret: flags.secret,
state,
key: String::new(),
value: String::new(),
key_id: text_input::Id::unique(),
@ -74,14 +71,11 @@ impl Application for Counter {
let key = std::mem::take(&mut self.key);
let value = std::mem::take(&mut self.value);
self.room
self.state
.lock()
.unwrap()
.create_event(
CoreContent::Custom(KVEventContent::Set(key, value)),
&self.secret,
)
.unwrap();
.create_event(CoreContent::Custom(KVEventContent::Set(key, value)))
.unwrap()
}
Message::FocusNext => {
let next = match &self.focused {
@ -106,13 +100,14 @@ impl Application for Counter {
}
fn view(&self) -> Element<'_, Self::Message> {
let mut room = self.room.lock().unwrap();
let store = MemoryStore::new()
.init(room.get_resolver().get_state_config())
.unwrap();
let state = room.resolve_state(*store);
dbg!(&state);
let rows = state.table("kv").unwrap().all().unwrap();
let mut state = self.state.lock().unwrap();
fn resolve<R: Resolver>(state: &mut State<R>) {
state.room.resolve_state(&mut *state.store);
}
resolve(&mut state);
let rows = state.store.table("kv").unwrap().all().unwrap();
let mut column = Column::new();
column = column.push(text(format!("{} keys", rows.len())).size(24));
for row in rows {
@ -159,22 +154,105 @@ impl Application for Counter {
}
}
fn main() {
let (_actor, secret) = ActorId::new(SignatureType::Ed25519);
let room = Room::builder()
.with_hasher(HashType::Sha256)
.with_signer(SignatureType::Ed25519)
.with_resolver(KVResolver)
.create(&secret)
.unwrap();
Counter::run(Settings {
#[derive(Debug)]
struct State<R: Resolver> {
db: Rc<rusqlite::Connection>,
room: Room<R>,
secret: ActorSecret,
store: Box<dag_resolve::store::sqlite::Database>,
}
impl<R: Resolver> State<R> {
fn create_event(&mut self, content: CoreContent<R::EventType>) -> Result<()> {
match self.room.create_event(content, &self.secret) {
Ok(event) => {
self.db.execute(
"INSERT INTO _events (id, json) VALUES (?, ?)",
(event.id().to_string(), serde_json::to_string(&event)?),
)?;
Ok(())
}
Err(dag_resolve::Error::AlreadyExists) => Ok(()),
Err(err) => Err(err.into()),
}
}
fn append_event(&mut self, event: Event<R::EventType>) -> Result<()> {
match self.room.append_event(event) {
Ok(event) => {
self.db.execute(
"INSERT INTO _events (id, json) VALUES (?, ?)",
(event.id().to_string(), serde_json::to_string(&event)?),
)?;
Ok(())
}
Err(dag_resolve::Error::AlreadyExists) => Ok(()),
Err(err) => Err(err.into()),
}
}
}
fn open(path: impl AsRef<Path>) -> Result<State<KVResolver>> {
// restore repo
let db = rusqlite::Connection::open(path)?;
let actor: ActorId = db.query_row(
"SELECT value FROM _config WHERE key='actor_id'",
[],
|row| row.get(0).map(|s: String| s.parse()),
)??;
let secret: ActorSecret = db.query_row(
"SELECT value FROM _config WHERE key='actor_secret'",
[],
|row| row.get(0).map(|s: String| serde_json::from_str(&s)),
)??;
dbg!(&actor, &secret);
let mut q = db.prepare("SELECT json FROM _events")?;
let mut rows = q.query([])?;
let event_json: String = rows
.next()?
.expect("there's always one root event")
.get(0)?;
let event: Event<()> = serde_json::from_str(&event_json)?;
match event.content() {
CoreContent::Create(c) => match c.resolver.as_str() {
"kv" => {
let mut room = Room::from_root(KVResolver, serde_json::from_str(&event_json)?)?;
while let Some(row) = rows.next()? {
let s: String = row.get(0)?;
let ev = serde_json::from_str(&s)?;
room.append_event(ev)?;
}
drop(event);
drop(rows);
drop(q);
dbg!(&room);
let db = Rc::new(db);
let store = dag_resolve::store::sqlite::Database::from_conn(db.clone())
.init(room.get_resolver().get_state_config())?;
Ok(State {
db,
room,
secret,
store,
})
}
_ => unimplemented!("unknown or unimplemented resolver"),
},
_ => panic!("tried to start with non-create event"),
}
}
fn main() -> Result<()> {
let state = open("room.db")?;
Main::run(Settings {
default_font: Font::with_name("Hind Siliguri"),
flags: Flags { room, secret },
flags: Flags { state },
id: None,
window: iced::window::Settings::default(),
default_text_size: 16.0,
antialiasing: false,
exit_on_close_request: true,
})
.unwrap();
})?;
Ok(())
}

View file

@ -65,15 +65,15 @@ impl<R: Resolver> Room<R> {
&self.resolver
}
pub fn resolve_state<S>(&mut self, initial_state: S) -> S
pub fn resolve_state<S>(&self, state: &mut S)
where
S: proto::table::State,
{
let resolver = self.get_resolver();
let sorted = sort(|a, b| resolver.tiebreak(a, b), &self.events);
let state = initial_state;
state.reset().unwrap();
for event in sorted {
let effects = resolver.resolve(&state, event);
let effects = resolver.resolve(state, event);
for effect in effects {
match effect {
ResolverEffect::Insert { table, row } => {
@ -82,7 +82,6 @@ impl<R: Resolver> Room<R> {
}
}
}
state
}
pub fn create_event(

View file

@ -109,7 +109,11 @@ pub trait State: Debug {
type Err: Debug;
type Table: Table;
/// Get a table
fn table(&self, name: &str) -> Result<Self::Table, Self::Err>;
/// TEMP: clear everything in the table. In the future, there will be methods for incremental reduction.
fn reset(&mut self) -> Result<(), Self::Err>;
}
pub trait Table {

View file

@ -40,6 +40,13 @@ impl State for MemoryStore {
fn table(&self, name: &str) -> Result<Self::Table, Self::Err> {
self.tables.get(name).cloned().ok_or(())
}
fn reset(&mut self) -> Result<(), Self::Err> {
for table in self.tables.values_mut() {
table.map.write().unwrap().clear();
}
Ok(())
}
}
impl Table for MemoryTable {

View file

@ -2,7 +2,7 @@
use std::{collections::HashMap, path::Path, rc::Rc};
use crate::proto::table;
use crate::proto::table::{self, State};
use rusqlite::params_from_iter;
use thiserror::Error;
@ -39,8 +39,30 @@ impl Database {
}
pub fn init(mut self, config: table::StateConfig) -> Result<Box<Self>, Error> {
self.config = Some(config);
self.reset()?;
Ok(Box::new(self))
}
}
impl table::State for Database {
type Table = Table;
type Err = Error;
fn table(&self, name: &str) -> Result<Table, Self::Err> {
let Some(table) = self.config.as_ref().unwrap().tables.get(name) else {
panic!("table does not exist");
};
Ok(Table {
connection: self.connection.clone(),
name: name.to_owned(),
config: table.to_owned(),
})
}
fn reset(&mut self) -> Result<(), Self::Err> {
let mut sql = String::new();
for (table_name, table_column) in &config.tables {
for (table_name, table_column) in &self.config.as_ref().unwrap().tables {
// FIXME: don't drop and resolve from scratch every time
sql.push_str("DROP TABLE IF EXISTS '");
sql.push_str(&table_name.replace('\'', "''"));
@ -64,24 +86,7 @@ impl Database {
sql.push_str(");\n");
}
self.connection.execute_batch(&sql).unwrap();
self.config = Some(config);
Ok(Box::new(self))
}
}
impl table::State for Database {
type Table = Table;
type Err = Error;
fn table(&self, name: &str) -> Result<Table, Self::Err> {
let Some(table) = self.config.as_ref().unwrap().tables.get(name) else {
panic!("table does not exist");
};
Ok(Table {
connection: self.connection.clone(),
name: name.to_owned(),
config: table.to_owned(),
})
Ok(())
}
}
@ -89,7 +94,7 @@ impl table::Table for Table {
type Err = Error;
fn all(&self) -> Result<Vec<table::TableRow>, Self::Err> {
let mut sql = String::from("SELECT * FROM ");
let mut sql = String::from("SELECT * FROM '");
sql.push_str(&self.name.replace('\'', "''"));
sql.push('\'');
let mut sql_query = self.connection.prepare(&sql).unwrap();