diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/db.rs | 121 | ||||
-rw-r--r-- | src/main.rs | 14 | ||||
-rw-r--r-- | src/posts.rs | 181 |
3 files changed, 140 insertions, 176 deletions
diff --git a/src/db.rs b/src/db.rs index 84f209b..90ff548 100644 --- a/src/db.rs +++ b/src/db.rs @@ -1,68 +1,94 @@ -use std::time; +use fd_lock::FdLock; +use serde::{Deserialize, Serialize}; + +use std::fs; +use std::fs::File; use crate::conf; use crate::error; -const DB_PATH: &str = "/usr/local/clinte/clinte.db"; +#[cfg(test)] +pub const PATH: &str = "clinte.json"; + +#[cfg(not(test))] +pub const PATH: &str = "/usr/local/clinte/clinte.json"; -#[derive(Debug)] +#[derive(Debug, Deserialize, Serialize)] pub struct Post { - pub id: u32, pub title: String, pub author: String, pub body: String, } +#[derive(Debug, Deserialize, Serialize)] +pub struct Posts { + pub posts: Vec<Post>, +} + #[derive(Debug)] pub struct Conn { - pub conn: rusqlite::Connection, + pub conn: FdLock<std::fs::File>, } impl Conn { - pub fn init(path: &str) -> rusqlite::Connection { - let start = time::Instant::now(); - + pub fn init(path: &str) -> Self { if *conf::DEBUG { - log::info!("Connecting to database"); + log::info!("Opening clinte.json"); } - let conn = error::helper( - rusqlite::Connection::open_with_flags( - path, - rusqlite::OpenFlags::SQLITE_OPEN_FULL_MUTEX - | rusqlite::OpenFlags::SQLITE_OPEN_CREATE - | rusqlite::OpenFlags::SQLITE_OPEN_READ_WRITE, - ), - "Could not connect to DB", - ); + let file = error::helper(File::open(path), "Couldn't open clinte.json"); - error::helper( - conn.execute( - "CREATE TABLE IF NOT EXISTS posts ( - id INTEGER PRIMARY KEY NOT NULL, - title TEXT NOT NULL, - author TEXT NOT NULL, - body TEXT NOT NULL - )", - rusqlite::NO_PARAMS, - ), - "Could not initialize DB", - ); + Self { + conn: FdLock::new(file), + } + } +} +impl Posts { + pub fn get_all(path: &str) -> Self { if *conf::DEBUG { - log::info!( - "Database connection established in {}ms", - start.elapsed().as_millis() - ); + log::info!("Retrieving posts..."); } - conn + let mut db = Conn::init(path); + let _guard = error::helper(db.conn.try_lock(), "Couldn't acquire lock on clinte.json"); + let strdata = error::helper(fs::read_to_string(PATH), "Couldn't read clinte.json"); + let out: Self = error::helper(serde_json::from_str(&strdata), "Couldn't parse clinte.json"); + + out } - pub fn new() -> Self { - Conn { - conn: Conn::init(DB_PATH), - } + pub fn replace(&mut self, n: usize, post: Post) { + self.posts[n] = post; + } + + pub fn get(&self, n: usize) -> &Post { + &self.posts[n] + } + + pub fn append(&mut self, post: Post) { + self.posts.push(post); + } + + pub fn delete(&mut self, n: usize) { + self.posts.remove(n); + } + + pub fn write(&self) { + let strdata = error::helper( + serde_json::to_string_pretty(&self), + "Couldn't serialize posts", + ); + + let mut db_fd = Conn::init(PATH); + let _guard = error::helper( + db_fd.conn.try_lock(), + "Couldn't acquire lock on clinte.json", + ); + error::helper( + fs::write(PATH, &strdata), + "Couldn't write data to clinte.json", + ); } } @@ -71,10 +97,19 @@ mod tests { use super::*; #[test] - fn test_new() { - let conn = Conn::init(":memory:"); - let mut stmt = conn.prepare("SELECT * FROM POSTS").unwrap(); + fn test_init() { + let mut conn = Conn::init(PATH); + conn.conn.try_lock().unwrap(); + } + + #[test] + fn retrieve_posts_and_examine() { + let all = Posts::get_all(PATH); + assert_eq!(all.posts.len(), 1); - stmt.query_map(rusqlite::NO_PARAMS, |_| Ok(())).unwrap(); + let post = all.get(0); + assert_eq!(post.title, "Welcome to CLI NoTEs!"); + assert_eq!(post.author, "clinte!"); + assert_eq!(post.body, "Welcome to clinte! For usage, run 'clinte -h'"); } } diff --git a/src/main.rs b/src/main.rs index f6d136d..6c38431 100644 --- a/src/main.rs +++ b/src/main.rs @@ -21,17 +21,15 @@ fn main() { println!("a community notices system"); println!(); - let db = db::Conn::new(); - if *conf::DEBUG { log::info!("Startup completed in {:?}ms", start.elapsed().as_millis()); } if arg_matches.subcommand_matches("post").is_some() { log::info!("New post..."); - error::helper(posts::create(&db), "Error creating new post"); + error::helper(posts::create(), "Error creating new post"); } else if let Some(updmatch) = arg_matches.subcommand_matches("update") { - let id: u32 = if let Some(val) = updmatch.value_of("id") { + let id: usize = if let Some(val) = updmatch.value_of("id") { error::helper(val.parse(), "Couldn't parse ID") } else { 0 @@ -40,11 +38,11 @@ fn main() { log::info!("Updating post ..."); error::helper( - posts::update_handler(&db, id), + posts::update_handler(id), format!("Error updating post {}", id).as_ref(), ); } else if let Some(delmatch) = arg_matches.subcommand_matches("delete") { - let id: u32 = if let Some(val) = delmatch.value_of("id") { + let id: usize = if let Some(val) = delmatch.value_of("id") { error::helper(val.parse(), "Couldn't parse ID") } else { 0 @@ -53,10 +51,10 @@ fn main() { log::info!("Deleting post"); error::helper( - posts::delete_handler(&db, id), + posts::delete_handler(id), format!("Error deleting post {}", id).as_ref(), ); } - error::helper(posts::display(&db), "Error displaying posts"); + error::helper(posts::display(), "Error displaying posts"); } diff --git a/src/posts.rs b/src/posts.rs index c9f9307..b286469 100644 --- a/src/posts.rs +++ b/src/posts.rs @@ -5,17 +5,6 @@ use crate::ed; use crate::error; use crate::user; -// Executes the sql statement that inserts a new post -// Broken off for unit testing. -pub fn exec_new(stmt: &mut rusqlite::Statement, title: &str, body: &str) -> error::Result<()> { - stmt.execute_named(&[ - (":title", &title), - (":author", &*user::NAME), - (":body", &body), - ])?; - Ok(()) -} - // Make sure nobody encodes narsty characters // into a message to negatively affect other // users @@ -29,11 +18,7 @@ fn str_to_utf8(str: &str) -> String { } // First handler for creating a new post. -pub fn create(db: &db::Conn) -> error::Result<()> { - let mut stmt = db - .conn - .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)")?; - +pub fn create() -> error::Result<()> { println!(); println!("Title of the new post: "); @@ -55,43 +40,38 @@ pub fn create(db: &db::Conn) -> error::Result<()> { } else { &body_raw }; - let trimmed_body = body.trim(); - exec_new(&mut stmt, title, trimmed_body)?; + let user = &*user::NAME; + + let mut all = db::Posts::get_all(db::PATH); + let new = db::Post { + author: user.into(), + title: title.to_string(), + body: trimmed_body.to_string(), + }; + + all.append(new); + all.write(); println!(); Ok(()) } // Shows the most recent posts. -pub fn display(db: &db::Conn) -> error::Result<()> { - let mut stmt = db.conn.prepare("SELECT * FROM posts")?; - let out = stmt.query_map(rusqlite::NO_PARAMS, |row| { - let id: u32 = row.get(0)?; - let title: String = row.get(1)?; - let author: String = row.get(2)?; - let body: String = row.get(3)?; - Ok(db::Post { - id, - title, - author, - body, - }) - })?; +pub fn display() -> error::Result<()> { + let all = db::Posts::get_all(db::PATH); let mut postvec = Vec::new(); - out.for_each(|row| { - if let Ok(post) = row { - let newpost = format!( - "{}. {} -> by {}\n{}\n\n", - post.id, - post.title.trim(), - post.author, - post.body.trim() - ); - postvec.push(newpost); - } + all.posts.iter().enumerate().for_each(|(id, post)| { + let newpost = format!( + "{}. {} -> by {}\n{}\n\n", + id + 1, + post.title.trim(), + post.author, + post.body.trim() + ); + postvec.push(newpost); }); for (i, e) in postvec.iter().enumerate() { @@ -104,8 +84,8 @@ pub fn display(db: &db::Conn) -> error::Result<()> { } // First handler to update posts. -pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> { - let id_num_in = if id == 0 { +pub fn update_handler(id: usize) -> error::Result<()> { + let mut id_num_in = if id == 0 { println!(); println!("ID number of your post to edit?"); let mut id_num_in = String::new(); @@ -115,31 +95,29 @@ pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> { id }; - let mut get_stmt = db.conn.prepare("SELECT * FROM posts WHERE id = :id")?; + id_num_in -= 1; - let row = get_stmt.query_row_named(&[(":id", &id_num_in)], |row| { - let title: String = row.get(1)?; - let author = row.get(2)?; - let body = row.get(3)?; - Ok(vec![title, author, body]) - })?; + let user = &*user::NAME; + let mut all = db::Posts::get_all(db::PATH); + let post = all.get(id_num_in); - if *user::NAME != row[1] { + if *user != post.author { + println!(); + println!("Users don't match. Can't update post!"); println!(); - println!("Username mismatch - can't update_handler post!"); - return Ok(()); + std::process::exit(1); } let mut new_title = String::new(); println!("Updating post {}", id_num_in); println!(); - println!("Current Title: {}", &row[0]); + println!("Current Title: {}", post.title); println!(); println!("Enter new title:"); io::stdin().read_line(&mut new_title)?; - let body_raw = str_to_utf8(&ed::call(&row[2])); + let body_raw = str_to_utf8(&ed::call(&post.body)); let body = if body_raw.len() > 500 { &body_raw[..500] } else { @@ -148,38 +126,24 @@ pub fn update_handler(db: &db::Conn, id: u32) -> error::Result<()> { let trimmed_body = body.trim(); - update(&new_title, &trimmed_body, id_num_in, &db)?; - - println!(); - Ok(()) -} - -// Allows editing of posts - called by main::update -pub fn update(new_title: &str, new_body: &str, id_num_in: u32, db: &db::Conn) -> error::Result<()> { - let new_title = new_title.trim(); - let new_body = new_body.trim(); - - let title_stmt = format!("UPDATE posts SET title = :title WHERE id = {}", id_num_in); - let mut stmt = db.conn.prepare(&title_stmt)?; - stmt.execute_named(&[(":title", &new_title)])?; - let body_stmt = format!("UPDATE posts SET body = :body WHERE id = {}", id_num_in); - let mut stmt = db.conn.prepare(&body_stmt)?; + all.replace( + id_num_in, + db::Post { + author: user.into(), + title: new_title, + body: trimmed_body.to_string(), + }, + ); - stmt.execute_named(&[(":body", &new_body)])?; - - Ok(()) -} - -// Helper to just run a sql statement. -pub fn exec_stmt_no_params(stmt: &mut rusqlite::Statement) -> error::Result<()> { - stmt.execute(rusqlite::NO_PARAMS)?; + all.write(); + println!(); Ok(()) } // First handler to remove a post -pub fn delete_handler(db: &db::Conn, id: u32) -> error::Result<()> { - let id_num_in: u32 = if id == 0 { +pub fn delete_handler(id: usize) -> error::Result<()> { + let mut id_num_in = if id == 0 { println!(); println!("ID of the post to delete?"); let mut id_num_in = String::new(); @@ -190,53 +154,20 @@ pub fn delete_handler(db: &db::Conn, id: u32) -> error::Result<()> { id }; - let del_stmt = format!("DELETE FROM posts WHERE id = {}", id_num_in); - let get_stmt = format!("SELECT * FROM posts WHERE id = {}", id_num_in); - - let mut get_stmt = db.conn.prepare(&get_stmt)?; - let mut del_stmt = db.conn.prepare(&del_stmt)?; + id_num_in -= 1; - let user_in_post: String = get_stmt.query_row(rusqlite::NO_PARAMS, |row| row.get(2))?; + let mut all = db::Posts::get_all(db::PATH); + let post = all.get(id_num_in); - if *user::NAME != user_in_post { + if *user::NAME != post.author { println!(); - println!("Users don't match. Can't delete!"); + println!("Users don't match. Can't delete post!"); println!(); - return Ok(()); + std::process::exit(1); } - exec_stmt_no_params(&mut del_stmt)?; - Ok(()) -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn post_new() { - let db = db::Conn::init(":memory:"); - let db = db::Conn { conn: db }; - let mut stmt = db - .conn - .prepare("INSERT INTO posts (title, author, body) VALUES (:title, :author, :body)") - .unwrap(); - - let title = String::from("TEST TITLE"); - - exec_new(&mut stmt, &title, "TEST BODY").unwrap(); - update("NEW TITLE", "TEST BODY", 1, &db).unwrap(); + all.delete(id_num_in); + all.write(); - let mut stmt = db - .conn - .prepare("SELECT * FROM posts WHERE title = :title") - .unwrap(); - - let title = String::from("NEW TITLE"); - let out: String = stmt - .query_row_named(&[(":title", &title)], |row| row.get::<usize, String>(1)) - .unwrap(); - - assert_eq!("NEW TITLE", &out); - } + Ok(()) } |