From 0bbe716d8d59db83c90801dc270500d5307b1994 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Sat, 25 Feb 2023 12:04:40 -0500 Subject: [PATCH] Going somewhere. --- resources/templates/actions.html | 2 +- resources/templates/index.html | 19 +++++ .../partials/actions/create_security.html | 36 +++++++--- .../partials/actions/submit_button.html | 2 +- src/main.rs | 69 ++++++++++--------- src/util.rs | 68 ++++++++++++++++-- src/webui/mod.rs | 12 ++-- src/webui/views.rs | 2 + src/webui/worker.rs | 9 +-- 9 files changed, 160 insertions(+), 59 deletions(-) diff --git a/resources/templates/actions.html b/resources/templates/actions.html index 6f52ddb..73bd315 100644 --- a/resources/templates/actions.html +++ b/resources/templates/actions.html @@ -22,7 +22,7 @@ - +
diff --git a/resources/templates/index.html b/resources/templates/index.html index 42ca34c..1239132 100644 --- a/resources/templates/index.html +++ b/resources/templates/index.html @@ -9,4 +9,23 @@ +
+
+ +
+
+ +
+ +
+ +
+
+ {% endblock %} \ No newline at end of file diff --git a/resources/templates/partials/actions/create_security.html b/resources/templates/partials/actions/create_security.html index b2da1c7..57edd6c 100644 --- a/resources/templates/partials/actions/create_security.html +++ b/resources/templates/partials/actions/create_security.html @@ -1,16 +1,32 @@ - - -
- +
+
+ +
+
+ +
-
diff --git a/resources/templates/partials/actions/submit_button.html b/resources/templates/partials/actions/submit_button.html index 8c671c2..5926571 100644 --- a/resources/templates/partials/actions/submit_button.html +++ b/resources/templates/partials/actions/submit_button.html @@ -5,5 +5,5 @@ hx-target="#event-table-first-row" hx-ext="json-enc" hx-swap="outerHTML"> - Submit ss + Submit \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 857fc6e..b656ec2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use std::path::PathBuf; +use clap::{Arg, ArgAction, ArgMatches, Command, value_parser}; use clap::parser::ValuesRef; -use clap::{value_parser, Arg, ArgAction, ArgMatches, Command}; use log::{debug, error, info}; use rusqlite::Connection; use serde::Serialize; @@ -11,7 +11,7 @@ use db::securities::list_securities; use crate::db::users::{create_user, list_users}; use crate::migration::apply_migrations; -use crate::util::{CustomResult, Error}; +use crate::util::{CustomResult, Error, SQLiteConnectionFactory}; use crate::webui::web_ui_main; mod db; @@ -57,7 +57,7 @@ trait CommandLineUserLogic { fn run_webui( &mut self, - conn: &mut Connection, + conn: SQLiteConnectionFactory, port: u16, enable_template_autoreload: bool, ) -> CustomResult<()>; @@ -251,14 +251,14 @@ fn process_process_events_command_line( } fn process_run_wui_command_line( - conn: &mut Connection, + connFact: SQLiteConnectionFactory, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic, ) -> CustomResult<()> { debug!("Processing run wui command line {:?}.", m); let port = m.get_one::("port").unwrap_or(&2222); let enable_template_autoreload = m.get_flag("enable-template-autoreload"); - logic.run_webui(conn, *port, enable_template_autoreload) + logic.run_webui(connFact, *port, enable_template_autoreload) } fn process_securities_command_line( @@ -290,30 +290,31 @@ fn process_events_command_line( } fn process_wui_command_line( - conn: &mut Connection, + connFact: SQLiteConnectionFactory, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic, ) -> CustomResult<()> { debug!("Processing wui command line {:?}.", m); match m.subcommand() { - Some(("run", run_wui_match)) => process_run_wui_command_line(conn, run_wui_match, logic), + Some(("run", run_wui_match)) => process_run_wui_command_line(connFact, run_wui_match, logic), _ => unreachable!(), } } fn process_command_line( - conn: &mut Connection, + connFact: SQLiteConnectionFactory, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic, ) -> CustomResult<()> { debug!("Processing command line {:?}.", m); + let mut conn = connFact.connection()?; match m.subcommand() { - Some(("users", user_match)) => process_user_command_line(conn, user_match, logic), + Some(("users", user_match)) => process_user_command_line(&mut conn, user_match, logic), Some(("securities", securities_match)) => { - process_securities_command_line(conn, securities_match, logic) + process_securities_command_line(&mut conn, securities_match, logic) } - Some(("events", events_match)) => process_events_command_line(conn, events_match, logic), - Some(("wui", wui_match)) => process_wui_command_line(conn, wui_match, logic), + Some(("events", events_match)) => process_events_command_line(&mut conn, events_match, logic), + Some(("wui", wui_match)) => process_wui_command_line(connFact, wui_match, logic), _ => { println!("Received un parsable command. {:?}", m.subcommand()); unreachable!(); @@ -377,7 +378,7 @@ mod cli_test { fn run_webui( &mut self, - _conn: &mut Connection, + _conn: SQLiteConnectionFactory, _port: u16, _enable_template_autoreload: bool, ) -> CustomResult<()> { @@ -419,7 +420,7 @@ mod cli_test { } #[test] - fn create_user() { + fn create_user() -> CustomResult<()> { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec![ "test", @@ -432,48 +433,53 @@ mod cli_test { ]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(SQLiteConnectionFactory::inMemory(), &m, &mut logic)?; assert_eq!(logic.create_user_count, 1); + Ok(()) } #[test] - fn list_users() { + fn list_users() -> CustomResult<()> { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "users", "ls"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(SQLiteConnectionFactory::inMemory(), &m, &mut logic)?; assert_eq!(logic.list_user_count, 1); + Ok(()) } #[test] - fn list_securities() { + fn list_securities() -> CustomResult<()> { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "securities", "ls"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(SQLiteConnectionFactory::inMemory(), &m, &mut logic)?; assert_eq!(logic.list_securities_count, 1); + Ok(()) } #[test] - fn process_events() { + fn process_events() -> CustomResult<()> { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "events", "process", "/dev/null"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(SQLiteConnectionFactory::inMemory(), &m, &mut logic)?; assert_eq!(logic.process_events_count, 1); + Ok(()) } #[test] - fn run_web_ui() { + fn run_web_ui() -> CustomResult<()> { let cmd = command_line_parser(); let m = cmd.get_matches_from(vec!["test", "wui", "run"]); let mut logic = TestCommandLineLogic::new(); let mut conn = Connection::open_in_memory().unwrap(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(SQLiteConnectionFactory::inMemory(), &m, &mut logic)?; assert_eq!(logic.run_webui_count, 1); + Ok(()) } } @@ -522,19 +528,20 @@ impl CommandLineUserLogic for ProCommandLineLogic { fn run_webui( &mut self, - conn: &mut Connection, + connFact: SQLiteConnectionFactory, port: u16, enable_template_autoreload: bool, ) -> CustomResult<()> { info!("Serving web-ui on port {:?}", port); - web_ui_main(conn, port, enable_template_autoreload) + web_ui_main(connFact, port, enable_template_autoreload) } } -fn get_connection(m: &ArgMatches) -> Connection { +fn get_connection(m: &ArgMatches) -> SQLiteConnectionFactory { let path = get_db_path(&m); + info!("Sqlite database location is {:?}.", path); - return Connection::open(path).unwrap(); + SQLiteConnectionFactory::new(path.clone()) } fn set_logger(m: &ArgMatches) { @@ -544,14 +551,14 @@ fn set_logger(m: &ArgMatches) { .unwrap(); } -fn main() { +fn main() -> CustomResult<()> { let command = command_line_parser(); let m = command.get_matches(); set_logger(&m); - let mut conn = get_connection(&m); + let connFactory = get_connection(&m); - apply_migrations(&mut conn).unwrap(); + apply_migrations(&mut connFactory.connection()?).unwrap(); let mut logic = ProCommandLineLogic::new(); - process_command_line(&mut conn, &m, &mut logic); + process_command_line(connFactory, &m, &mut logic) } diff --git a/src/util.rs b/src/util.rs index 346140b..924125e 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,4 +1,5 @@ use std::io; +use std::path::PathBuf; use rusqlite::{Connection, OpenFlags}; @@ -17,20 +18,73 @@ pub enum Error { pub type CustomResult = Result; +#[derive(Clone)] +struct PathSQLiteConnectionCreator { + path: PathBuf, +} + +impl PathSQLiteConnectionCreator { + fn connection(&self) -> CustomResult { + Connection::open(&self.path).map_err(Error::SqlError) + } + + fn readonly(&self) -> CustomResult { + let flags = OpenFlags::SQLITE_OPEN_READ_ONLY; + Connection::open_with_flags(&self.path, flags).map_err(Error::SqlError) + } +} + +#[derive(Clone)] +struct EphemeralSQLiteConnectionCreator {} + +// todo: fix this horrible implementation. All connections +// must share the same data. +impl EphemeralSQLiteConnectionCreator { + fn connection(&self) -> CustomResult { + Connection::open_in_memory().map_err(Error::SqlError) + } + + fn readonly(&self) -> CustomResult { + let flags = OpenFlags::SQLITE_OPEN_READ_ONLY; + Connection::open_in_memory_with_flags(flags).map_err(Error::SqlError) + } +} + +#[derive(Clone)] +enum SQLiteConnectionCreator { + Path(PathSQLiteConnectionCreator), + Ephemeral(EphemeralSQLiteConnectionCreator), +} + +#[derive(Clone)] pub struct SQLiteConnectionFactory { - path: String, + creator: SQLiteConnectionCreator, } impl SQLiteConnectionFactory { - pub fn new(path: String) -> SQLiteConnectionFactory { - SQLiteConnectionFactory { path } + pub fn new(path: PathBuf) -> SQLiteConnectionFactory { + SQLiteConnectionFactory { + creator: SQLiteConnectionCreator::Path(PathSQLiteConnectionCreator { path }) + } } - fn connection(&self) -> Connection { - Connection::open(&self.path).unwrap() + pub fn inMemory() -> SQLiteConnectionFactory { + SQLiteConnectionFactory { + creator: SQLiteConnectionCreator::Ephemeral(EphemeralSQLiteConnectionCreator {}) + } } - fn readonly(&self) -> Connection { - Connection::open_with_flags(&self.path, OpenFlags::SQLITE_OPEN_READ_ONLY).unwrap() + pub fn connection(&self) -> CustomResult { + match & self.creator { + SQLiteConnectionCreator::Path(c) => c.connection(), + SQLiteConnectionCreator::Ephemeral(c) => c.connection(), + } + } + + pub fn readonly(&self) -> CustomResult { + match & self.creator { + SQLiteConnectionCreator::Path(c) => c.readonly(), + SQLiteConnectionCreator::Ephemeral(c) => c.readonly(), + } } } diff --git a/src/webui/mod.rs b/src/webui/mod.rs index 2ef4780..86b60a9 100644 --- a/src/webui/mod.rs +++ b/src/webui/mod.rs @@ -18,7 +18,7 @@ use minijinja_autoreload::AutoReloader; use rusqlite::Connection; use crate::events::Event; -use crate::util::{CustomResult, Error}; +use crate::util::{CustomResult, Error, SQLiteConnectionFactory}; use crate::webui::worker::{bob, event_processor_loop}; mod views; @@ -83,20 +83,22 @@ fn create_auto_reloader(enable_template_autoreload: bool) -> AutoReloader { } #[tokio::main] -pub async fn web_ui_main(conn: &mut Connection, +pub async fn web_ui_main(connFact: SQLiteConnectionFactory, port: u16, enable_template_autoreload: bool) -> CustomResult<()> { // The closure is invoked every time the environment is outdated to recreate it. let tmpl_reloader = create_auto_reloader(enable_template_autoreload); - let (s, r) = async_channel::unbounded::(); + let (event_sender, event_receiver) = async_channel::unbounded::(); let tmpl_reloader = web::Data::new(tmpl_reloader); + let clonedConnFact = connFact.clone(); let server = HttpServer::new(move || { App::new() .app_data(tmpl_reloader.clone()) - .app_data(web::Data::new(s.clone())) + .app_data(web::Data::new(event_sender.clone())) + .app_data(web::Data::new(clonedConnFact.clone())) .service( actix_files::Files::new("/static", "./resources/static/").use_last_modified(true), ) @@ -114,7 +116,7 @@ pub async fn web_ui_main(conn: &mut Connection, .unwrap(); let t1 = server.run().fuse(); - let t2 = event_processor_loop(conn, r).fuse(); + let t2 = event_processor_loop(connFact, event_receiver).fuse(); let t3 = bob().fuse(); pin_mut!(t1, t2, t3); diff --git a/src/webui/views.rs b/src/webui/views.rs index 36d683a..3d274dc 100644 --- a/src/webui/views.rs +++ b/src/webui/views.rs @@ -7,6 +7,7 @@ use minijinja::context; use serde::Deserialize; use crate::events::Event; +use crate::util::SQLiteConnectionFactory; use crate::webui::MiniJinjaRenderer; #[actix_web::get("/")] @@ -41,6 +42,7 @@ pub async fn actions_partials( _req: HttpRequest, tmpl_env: MiniJinjaRenderer, query: web::Query>, + connFactory: web::Data, ) -> actix_web::Result { let event_type = query .get("event_type") diff --git a/src/webui/worker.rs b/src/webui/worker.rs index 68357e4..36c9b9c 100644 --- a/src/webui/worker.rs +++ b/src/webui/worker.rs @@ -6,17 +6,18 @@ use tokio::time::sleep; use crate::event_processor::process_event; use crate::events::Event; -use crate::util::CustomResult; +use crate::util::{CustomResult, SQLiteConnectionFactory}; use crate::util::Error::FuckingError; -pub async fn event_processor_loop(conn: &mut Connection, +// todo, all the "?" and handle the exceptions for real +pub async fn event_processor_loop(conFact: SQLiteConnectionFactory, r: async_channel::Receiver) -> CustomResult<()> { loop { let res = r.recv().await; + let mut conn = conFact.connection()?; match res { - Ok(event) => process_event(conn, event)?, // todo, remove "?" + Ok(event) => process_event(&mut conn, event)?, Err(e) => { - println!("Error: {:?}", e); return Err(FuckingError(e.to_string())); } }