Bubble up all db errors. Implement Deref and DerefMut for DBConn

This commit is contained in:
Joseph Ferano 2023-06-15 16:31:29 +07:00
parent f5b39592a4
commit f951e500f1
3 changed files with 138 additions and 115 deletions

View File

@ -63,14 +63,13 @@ pub struct State<'a> {
impl<'a> State<'a> { impl<'a> State<'a> {
/// Creates a new [`State`]. /// Creates a new [`State`].
/// ///
/// # Panics /// # Errors
/// ///
/// Panics if we can't get all the columns from the database /// Returns an error if we can't read the database columns
#[must_use]
pub fn new(conn: Connection) -> Result<Self, Error> { pub fn new(conn: Connection) -> Result<Self, Error> {
let db_conn = db::DBConn::new(conn); let db_conn = db::DBConn::new(conn);
let columns = db_conn.get_all_columns()?; let columns = db_conn.get_all_columns()?;
let selected_column = db_conn.get_selected_column(); let selected_column = db_conn.get_selected_column()?;
Ok(State { Ok(State {
columns, columns,
selected_column_idx: selected_column, selected_column_idx: selected_column,
@ -89,14 +88,14 @@ impl<'a> State<'a> {
&mut self.columns[self.selected_column_idx] &mut self.columns[self.selected_column_idx]
} }
pub fn select_previous_column(&mut self) { pub fn select_previous_column(&mut self) -> Result<(), Error> {
self.selected_column_idx = self.selected_column_idx.saturating_sub(1); self.selected_column_idx = self.selected_column_idx.saturating_sub(1);
self.db_conn.set_selected_column(self.selected_column_idx); self.db_conn.set_selected_column(self.selected_column_idx)
} }
pub fn select_next_column(&mut self) { pub fn select_next_column(&mut self) -> Result<(), Error> {
self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1); self.selected_column_idx = min(self.selected_column_idx + 1, self.columns.len() - 1);
self.db_conn.set_selected_column(self.selected_column_idx); self.db_conn.set_selected_column(self.selected_column_idx)
} }
#[must_use] #[must_use]
@ -126,16 +125,18 @@ impl<'a> State<'a> {
column.tasks.get_mut(column.selected_task_idx) column.tasks.get_mut(column.selected_task_idx)
} }
pub fn select_previous_task(&mut self) { pub fn select_previous_task(&mut self) -> Result<(), Error> {
let column = self.get_selected_column_mut(); let column = self.get_selected_column_mut();
column.selected_task_idx = column.selected_task_idx.saturating_sub(1); column.selected_task_idx = column.selected_task_idx.saturating_sub(1);
let task_idx = column.selected_task_idx; let task_idx = column.selected_task_idx;
let col_id = column.id; let col_id = column.id;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
Ok(())
} }
pub fn select_next_task(&mut self) { pub fn select_next_task(&mut self) -> Result<(), Error> {
let column = self.get_selected_column_mut(); let column = self.get_selected_column_mut();
column.selected_task_idx = min( column.selected_task_idx = min(
column.selected_task_idx + 1, column.selected_task_idx + 1,
@ -144,25 +145,31 @@ impl<'a> State<'a> {
let task_idx = column.selected_task_idx; let task_idx = column.selected_task_idx;
let col_id = column.id; let col_id = column.id;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
Ok(())
} }
pub fn select_first_task(&mut self) { pub fn select_first_task(&mut self) -> Result<(), Error> {
let column = self.get_selected_column_mut(); let column = self.get_selected_column_mut();
column.selected_task_idx = 0; column.selected_task_idx = 0;
let task_idx = column.selected_task_idx; let task_idx = column.selected_task_idx;
let col_id = column.id; let col_id = column.id;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
Ok(())
} }
pub fn select_last_task(&mut self) { pub fn select_last_task(&mut self) -> Result<(), Error> {
let column = self.get_selected_column_mut(); let column = self.get_selected_column_mut();
column.selected_task_idx = column.tasks.len().saturating_sub(1); column.selected_task_idx = column.tasks.len().saturating_sub(1);
let task_idx = column.selected_task_idx; let task_idx = column.selected_task_idx;
let col_id = column.id; let col_id = column.id;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
Ok(())
} }
#[must_use] #[must_use]
@ -175,16 +182,16 @@ impl<'a> State<'a> {
}) })
} }
pub fn move_task_up(&mut self) { pub fn move_task_up(&mut self) -> Result<(), Error> {
self.move_task(false); self.move_task(false)
} }
pub fn move_task_down(&mut self) { pub fn move_task_down(&mut self) -> Result<(), Error> {
self.move_task(true); self.move_task(true)
} }
/// Returns the move task down of this [`State`]. /// Returns the move task down of this [`State`].
pub fn move_task(&mut self, is_down: bool) { pub fn move_task(&mut self, is_down: bool) -> Result<(), Error> {
let other_task = if is_down { let other_task = if is_down {
self.get_next_task() self.get_next_task()
} else { } else {
@ -205,74 +212,80 @@ impl<'a> State<'a> {
} }
let col_id = column.id; let col_id = column.id;
self.db_conn.swap_task_order(t2_id, t1_id); self.db_conn.swap_task_order(t2_id, t1_id)?;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
} }
Ok(())
} }
pub fn move_task_previous_column(&mut self) { pub fn move_task_previous_column(&mut self) -> Result<(), Error> {
self.move_task_to_column(false); self.move_task_to_column(false)
} }
pub fn move_task_next_column(&mut self) { pub fn move_task_next_column(&mut self) -> Result<(), Error> {
self.move_task_to_column(true); self.move_task_to_column(true)
} }
fn move_task_to_column(&mut self, move_right: bool) { fn move_task_to_column(&mut self, move_right: bool) -> Result<(), Error> {
let can_move_right = move_right && self.selected_column_idx < self.columns.len() - 1; let can_move_right = move_right && self.selected_column_idx < self.columns.len() - 1;
let can_move_left = !move_right && self.selected_column_idx > 0; let can_move_left = !move_right && self.selected_column_idx > 0;
let first_col = self.get_selected_column_mut(); let first_col = self.get_selected_column_mut();
if first_col.tasks.is_empty() || !can_move_right && !can_move_left { if first_col.tasks.is_empty() || !can_move_right && !can_move_left {
return; // We're at the bounds so just ignore
return Ok(());
} }
let t = first_col.tasks.remove(first_col.selected_task_idx); let t = first_col.tasks.remove(first_col.selected_task_idx);
// Only move it if it was the last task // Only move it if it was the last task
if first_col.selected_task_idx == first_col.tasks.len() { if first_col.selected_task_idx == first_col.tasks.len() {
self.select_previous_task(); self.select_previous_task()?;
} }
if move_right { if move_right {
self.select_next_column(); self.select_next_column()?;
} else { } else {
self.select_previous_column(); self.select_previous_column()?;
} }
let col = self.get_selected_column_mut(); let col = self.get_selected_column_mut();
col.tasks.push(t); col.tasks.push(t);
self.select_last_task(); self.select_last_task()?;
if let Some(task) = self.get_selected_task() { if let Some(task) = self.get_selected_task() {
let col = self.get_selected_column(); let col = self.get_selected_column();
self.db_conn.move_task_to_column(task, col); self.db_conn.move_task_to_column(task, col)?;
self.db_conn.set_selected_column(self.selected_column_idx); self.db_conn.set_selected_column(self.selected_column_idx)?;
} }
Ok(())
} }
pub fn add_new_task(&mut self, title: String, description: String) { pub fn add_new_task(&mut self, title: String, description: String) -> Result<(), Error> {
let col_id = self.get_selected_column().id; let col_id = self.get_selected_column().id;
let task = self.db_conn.insert_new_task(title, description, col_id); let task = self.db_conn.create_new_task(title, description, col_id)?;
self.select_last_task(); self.select_last_task()?;
let selected_task_idx = self.get_selected_column().selected_task_idx; let selected_task_idx = self.get_selected_column().selected_task_idx;
self.db_conn self.db_conn
.set_selected_task_for_column(selected_task_idx, col_id); .set_selected_task_for_column(selected_task_idx, col_id)?;
self.get_selected_column_mut().tasks.push(task); self.get_selected_column_mut().tasks.push(task);
self.select_last_task() self.select_last_task()?;
Ok(())
} }
pub fn edit_task(&mut self, title: String, description: String) { pub fn edit_task(&mut self, title: String, description: String) -> Result<(), Error> {
if let Some(selected_task) = self.get_selected_task_mut() { if let Some(selected_task) = self.get_selected_task_mut() {
selected_task.title = title; selected_task.title = title;
selected_task.description = description; selected_task.description = description;
let cloned = selected_task.clone(); let cloned = selected_task.clone();
self.db_conn.update_task_text(&cloned); self.db_conn.update_task_text(&cloned)?;
} }
Ok(())
} }
/// Delete the currently selected task from the selected column /// Delete the currently selected task from the selected column
pub fn delete_task(&mut self) { pub fn delete_task(&mut self) -> Result<(), Error> {
if let Some(task) = self.get_selected_task() { if let Some(task) = self.get_selected_task() {
let task_id = task.id; let task_id = task.id;
let column = self.get_selected_column_mut(); let column = self.get_selected_column_mut();
@ -282,12 +295,14 @@ impl<'a> State<'a> {
column.tasks.remove(task_idx); column.tasks.remove(task_idx);
if column.selected_task_idx >= column.tasks.len() { if column.selected_task_idx >= column.tasks.len() {
self.select_previous_task(); self.select_previous_task()?;
task_idx = task_idx.saturating_sub(1); task_idx = task_idx.saturating_sub(1);
} }
self.db_conn.delete_task(task_id); self.db_conn.delete_task(task_id)?;
self.db_conn.set_selected_task_for_column(task_idx, col_id); self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
} }
Ok(())
} }
} }

107
src/db.rs
View File

@ -1,8 +1,24 @@
use anyhow::Error;
use std::ops::{Deref, DerefMut};
use crate::{Column, Task}; use crate::{Column, Task};
use rusqlite::{params, Connection, Result}; use rusqlite::{params, Connection, Result};
pub struct DBConn(Connection); pub struct DBConn(Connection);
impl DerefMut for DBConn {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Deref for DBConn {
type Target = Connection;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DBConn { impl DBConn {
pub fn new(conn: Connection) -> Self { pub fn new(conn: Connection) -> Self {
DBConn(conn) DBConn(conn)
@ -14,7 +30,7 @@ impl DBConn {
/// ///
/// This function will return an error if something is wrong with the SQL /// This function will return an error if something is wrong with the SQL
pub fn get_tasks_by_column(&self, column_name: &String) -> Result<Vec<Task>> { pub fn get_tasks_by_column(&self, column_name: &String) -> Result<Vec<Task>> {
let mut stmt = self.0.prepare( let mut stmt = self.prepare(
r#" r#"
select task.id, title, description from task select task.id, title, description from task
join kb_column on column_id = kb_column.id join kb_column on column_id = kb_column.id
@ -42,9 +58,7 @@ impl DBConn {
/// ///
/// This function will return an error if there are issues with the SQL /// This function will return an error if there are issues with the SQL
pub fn get_all_columns(&self) -> Result<Vec<Column>> { pub fn get_all_columns(&self) -> Result<Vec<Column>> {
let mut stmt = self let mut stmt = self.prepare("select id, name, selected_task from kb_column")?;
.0
.prepare("select id, name, selected_task from kb_column")?;
let columns = stmt let columns = stmt
.query_map((), |row| { .query_map((), |row| {
let name = row.get(1)?; let name = row.get(1)?;
@ -65,19 +79,21 @@ impl DBConn {
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn insert_new_task(&self, title: String, description: String, column_id: i64) -> Task { pub fn create_new_task(
let mut stmt = self &self,
.0 title: String,
.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)") description: String,
.unwrap(); column_id: i64,
stmt.execute(params![title, description, column_id]) ) -> Result<Task> {
.unwrap(); let mut stmt =
let id = self.0.last_insert_rowid(); self.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)")?;
Task { stmt.execute(params![title, description, column_id])?;
let id = self.last_insert_rowid();
Ok(Task {
id, id,
title, title,
description, description,
} })
} }
/// . /// .
@ -85,9 +101,10 @@ impl DBConn {
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn delete_task(&self, task_id: i64) { pub fn delete_task(&self, task_id: i64) -> Result<()> {
let mut stmt = self.0.prepare("delete from task where id = ?1").unwrap(); let mut stmt = self.prepare("delete from task where id = ?1")?;
stmt.execute([task_id]).unwrap(); stmt.execute([task_id])?;
Ok(())
} }
/// . /// .
@ -95,13 +112,11 @@ impl DBConn {
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn update_task_text(&self, task: &Task) { pub fn update_task_text(&self, task: &Task) -> Result<()> {
let mut stmt = self let mut stmt = self
.0 .prepare("update task set title = ?2, description = ?3 where id = ?1")?;
.prepare("update task set title = ?2, description = ?3 where id = ?1") stmt.execute((&task.id, &task.title, &task.description))?;
.unwrap(); Ok(())
stmt.execute((&task.id, &task.title, &task.description))
.unwrap();
} }
/// . /// .
@ -109,9 +124,8 @@ impl DBConn {
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn move_task_to_column(&self, task: &Task, target_column: &Column) { pub fn move_task_to_column(&self, task: &Task, target_column: &Column) -> Result<()> {
let mut stmt = self let mut stmt = self
.0
.prepare( .prepare(
"update task "update task
set set
@ -124,7 +138,8 @@ impl DBConn {
) )
.unwrap(); .unwrap();
stmt.execute((&task.id, &target_column.id)).unwrap(); stmt.execute((&task.id, &target_column.id)).unwrap();
self.set_selected_task_for_column(target_column.selected_task_idx, target_column.id); self.set_selected_task_for_column(target_column.selected_task_idx, target_column.id)?;
Ok(())
} }
/// . /// .
@ -132,8 +147,8 @@ impl DBConn {
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) { pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> {
let tx = self.0.transaction().unwrap(); let tx = self.transaction().unwrap();
tx.execute( tx.execute(
"create temp table temp_order as select sort_order from task where id = ?1", "create temp table temp_order as select sort_order from task where id = ?1",
@ -156,54 +171,46 @@ impl DBConn {
tx.execute("drop table temp_order", ()).unwrap(); tx.execute("drop table temp_order", ()).unwrap();
tx.commit().unwrap(); tx.commit().unwrap();
Ok(())
} }
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn set_selected_column(&self, column_id: usize) { pub fn set_selected_column(&self, column_id: usize) -> Result<(), Error> {
let mut stmt = self let mut stmt = self
.0 .prepare("insert or replace into app_state(key, value) values (?1, ?2)")?;
.prepare("insert or replace into app_state(key, value) values (?1, ?2)") stmt.execute((&"selected_column", column_id.to_string()))?;
.unwrap(); Ok(())
stmt.execute((&"selected_column", column_id.to_string()))
.unwrap();
} }
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn get_selected_column(&self) -> usize { pub fn get_selected_column(&self) -> Result<usize> {
let mut stmt = self let mut stmt = self
.0 .prepare("select value from app_state where key = ?1")?;
.prepare("select value from app_state where key = ?1")
.unwrap();
stmt.query_row(["selected_column"], |row| { stmt.query_row(["selected_column"], |row| {
let value: String = row.get::<usize, String>(0).unwrap(); let value: String = row.get::<usize, String>(0).unwrap();
Ok(value.parse::<usize>().unwrap()) Ok(value.parse::<usize>().unwrap())
}) })
.unwrap()
} }
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) { pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> {
let mut stmt = self let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?;
.0 stmt.execute((column_id, task_idx))?;
.prepare("update kb_column set selected_task = ?2 where id = ?1") Ok(())
.unwrap();
stmt.execute((column_id, task_idx)).unwrap();
} }
/// # Panics /// # Panics
/// ///
/// Panics if something goes wrong with the SQL /// Panics if something goes wrong with the SQL
pub fn get_selected_task_for_column(&self, column_id: i32) -> usize { pub fn get_selected_task_for_column(&self, column_id: i32) -> Result<usize> {
let mut stmt = self let mut stmt = self
.0 .prepare("select selected_task from kb_column where key = ?1")?;
.prepare("select selected_task from kb_column where key = ?1") stmt.query_row([column_id], |row| row.get(0))
.unwrap();
stmt.query_row([column_id], |row| row.get(0)).unwrap()
} }
} }

View File

@ -2,8 +2,7 @@ use crate::app::{State, TaskEditFocus, TaskState};
use crossterm::event; use crossterm::event;
use crossterm::event::{Event, KeyCode}; use crossterm::event::{Event, KeyCode};
// pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent, mut task: TaskState<'_>) { pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), anyhow::Error> {
pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) {
if let Some(mut task) = state.task_edit_state.take() { if let Some(mut task) = state.task_edit_state.take() {
let mut clear_task = false; let mut clear_task = false;
match task.focus { match task.focus {
@ -30,9 +29,9 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) {
let title = task.title.clone().into_lines().join("\n"); let title = task.title.clone().into_lines().join("\n");
let description = task.description.clone().into_lines().join("\n"); let description = task.description.clone().into_lines().join("\n");
if task.is_edit { if task.is_edit {
state.edit_task(title, description); state.edit_task(title, description)?;
} else { } else {
state.add_new_task(title, description); state.add_new_task(title, description)?;
} }
clear_task = true; clear_task = true;
} }
@ -51,59 +50,61 @@ pub fn handle_task_edit(state: &mut State<'_>, key: event::KeyEvent) {
state.task_edit_state = Some(task); state.task_edit_state = Some(task);
} }
} }
Ok(())
} }
pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) { pub fn handle_main(state: &mut State<'_>, key: event::KeyEvent) -> Result<(), anyhow::Error> {
match key.code { match key.code {
KeyCode::Char('q') => state.quit = true, KeyCode::Char('q') => state.quit = true,
KeyCode::Char('h') | KeyCode::Left => { KeyCode::Char('h') | KeyCode::Left => {
state.select_previous_column(); state.select_previous_column()?;
} }
KeyCode::Char('j') | KeyCode::Down => { KeyCode::Char('j') | KeyCode::Down => {
state.select_next_task(); state.select_next_task()?;
} }
KeyCode::Char('k') | KeyCode::Up => { KeyCode::Char('k') | KeyCode::Up => {
state.select_previous_task(); state.select_previous_task()?;
} }
KeyCode::Char('l') | KeyCode::Right => { KeyCode::Char('l') | KeyCode::Right => {
state.select_next_column(); state.select_next_column()?;
} }
KeyCode::Char('g') => { KeyCode::Char('g') => {
state.select_first_task(); state.select_first_task()?;
} }
KeyCode::Char('G') => { KeyCode::Char('G') => {
state.select_last_task(); state.select_last_task()?;
} }
KeyCode::Char('H') => { KeyCode::Char('H') => {
state.move_task_previous_column(); state.move_task_previous_column()?;
} }
KeyCode::Char('L') => { KeyCode::Char('L') => {
state.move_task_next_column(); state.move_task_next_column()?;
} }
KeyCode::Char('J') => { KeyCode::Char('J') => {
state.move_task_down(); state.move_task_down()?;
} }
KeyCode::Char('K') => { KeyCode::Char('K') => {
state.move_task_up(); state.move_task_up()?;
} }
KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()), KeyCode::Char('n') => state.task_edit_state = Some(TaskState::default()),
KeyCode::Char('e') => state.task_edit_state = state.get_task_state_from_current(), KeyCode::Char('e') => state.task_edit_state = state.get_task_state_from_current(),
KeyCode::Char('D') => { KeyCode::Char('D') => {
state.delete_task(); state.delete_task()?;
} }
_ => {} _ => {}
} }
Ok(())
} }
/// # Errors /// # Errors
/// ///
/// Crossterm `event::read()` might return an error /// Crossterm `event::read()` might return an error
pub fn handle(state: &mut State<'_>) -> Result<(), std::io::Error> { pub fn handle(state: &mut State<'_>) -> Result<(), anyhow::Error> {
if let Event::Key(key) = event::read()? { if let Event::Key(key) = event::read()? {
if state.task_edit_state.is_some() { if state.task_edit_state.is_some() {
handle_task_edit(state, key); handle_task_edit(state, key)?;
} else { } else {
handle_main(state, key); handle_main(state, key)?;
} }
} }
Ok(()) Ok(())