about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db.rs121
-rw-r--r--src/main.rs14
-rw-r--r--src/posts.rs181
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(())
 }