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> {
/// Creates a new [`State`].
///
/// # Panics
/// # Errors
///
/// Panics if we can't get all the columns from the database
#[must_use]
/// Returns an error if we can't read the database columns
pub fn new(conn: Connection) -> Result<Self, Error> {
let db_conn = db::DBConn::new(conn);
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 {
columns,
selected_column_idx: selected_column,
@ -89,14 +88,14 @@ impl<'a> State<'a> {
&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.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.db_conn.set_selected_column(self.selected_column_idx);
self.db_conn.set_selected_column(self.selected_column_idx)
}
#[must_use]
@ -126,16 +125,18 @@ impl<'a> State<'a> {
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();
column.selected_task_idx = column.selected_task_idx.saturating_sub(1);
let task_idx = column.selected_task_idx;
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();
column.selected_task_idx = min(
column.selected_task_idx + 1,
@ -144,25 +145,31 @@ impl<'a> State<'a> {
let task_idx = column.selected_task_idx;
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();
column.selected_task_idx = 0;
let task_idx = column.selected_task_idx;
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();
column.selected_task_idx = column.tasks.len().saturating_sub(1);
let task_idx = column.selected_task_idx;
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]
@ -175,16 +182,16 @@ impl<'a> State<'a> {
})
}
pub fn move_task_up(&mut self) {
self.move_task(false);
pub fn move_task_up(&mut self) -> Result<(), Error> {
self.move_task(false)
}
pub fn move_task_down(&mut self) {
self.move_task(true);
pub fn move_task_down(&mut self) -> Result<(), Error> {
self.move_task(true)
}
/// 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 {
self.get_next_task()
} else {
@ -205,74 +212,80 @@ impl<'a> State<'a> {
}
let col_id = column.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.swap_task_order(t2_id, t1_id)?;
self.db_conn
.set_selected_task_for_column(task_idx, col_id)?;
}
Ok(())
}
pub fn move_task_previous_column(&mut self) {
self.move_task_to_column(false);
pub fn move_task_previous_column(&mut self) -> Result<(), Error> {
self.move_task_to_column(false)
}
pub fn move_task_next_column(&mut self) {
self.move_task_to_column(true);
pub fn move_task_next_column(&mut self) -> Result<(), Error> {
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_left = !move_right && self.selected_column_idx > 0;
let first_col = self.get_selected_column_mut();
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);
// Only move it if it was the last task
if first_col.selected_task_idx == first_col.tasks.len() {
self.select_previous_task();
self.select_previous_task()?;
}
if move_right {
self.select_next_column();
self.select_next_column()?;
} else {
self.select_previous_column();
self.select_previous_column()?;
}
let col = self.get_selected_column_mut();
col.tasks.push(t);
self.select_last_task();
self.select_last_task()?;
if let Some(task) = self.get_selected_task() {
let col = self.get_selected_column();
self.db_conn.move_task_to_column(task, col);
self.db_conn.set_selected_column(self.selected_column_idx);
self.db_conn.move_task_to_column(task, col)?;
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 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;
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.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() {
selected_task.title = title;
selected_task.description = description;
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
pub fn delete_task(&mut self) {
pub fn delete_task(&mut self) -> Result<(), Error> {
if let Some(task) = self.get_selected_task() {
let task_id = task.id;
let column = self.get_selected_column_mut();
@ -282,12 +295,14 @@ impl<'a> State<'a> {
column.tasks.remove(task_idx);
if column.selected_task_idx >= column.tasks.len() {
self.select_previous_task();
self.select_previous_task()?;
task_idx = task_idx.saturating_sub(1);
}
self.db_conn.delete_task(task_id);
self.db_conn.set_selected_task_for_column(task_idx, col_id);
self.db_conn.delete_task(task_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 rusqlite::{params, Connection, Result};
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 {
pub fn new(conn: Connection) -> Self {
DBConn(conn)
@ -14,7 +30,7 @@ impl DBConn {
///
/// 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>> {
let mut stmt = self.0.prepare(
let mut stmt = self.prepare(
r#"
select task.id, title, description from task
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
pub fn get_all_columns(&self) -> Result<Vec<Column>> {
let mut stmt = self
.0
.prepare("select id, name, selected_task from kb_column")?;
let mut stmt = self.prepare("select id, name, selected_task from kb_column")?;
let columns = stmt
.query_map((), |row| {
let name = row.get(1)?;
@ -65,19 +79,21 @@ impl DBConn {
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn insert_new_task(&self, title: String, description: String, column_id: i64) -> Task {
let mut stmt = self
.0
.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)")
.unwrap();
stmt.execute(params![title, description, column_id])
.unwrap();
let id = self.0.last_insert_rowid();
Task {
pub fn create_new_task(
&self,
title: String,
description: String,
column_id: i64,
) -> Result<Task> {
let mut stmt =
self.prepare("insert into task(title, description, column_id) values (?1, ?2, ?3)")?;
stmt.execute(params![title, description, column_id])?;
let id = self.last_insert_rowid();
Ok(Task {
id,
title,
description,
}
})
}
/// .
@ -85,9 +101,10 @@ impl DBConn {
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn delete_task(&self, task_id: i64) {
let mut stmt = self.0.prepare("delete from task where id = ?1").unwrap();
stmt.execute([task_id]).unwrap();
pub fn delete_task(&self, task_id: i64) -> Result<()> {
let mut stmt = self.prepare("delete from task where id = ?1")?;
stmt.execute([task_id])?;
Ok(())
}
/// .
@ -95,13 +112,11 @@ impl DBConn {
/// # Panics
///
/// 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
.0
.prepare("update task set title = ?2, description = ?3 where id = ?1")
.unwrap();
stmt.execute((&task.id, &task.title, &task.description))
.unwrap();
.prepare("update task set title = ?2, description = ?3 where id = ?1")?;
stmt.execute((&task.id, &task.title, &task.description))?;
Ok(())
}
/// .
@ -109,9 +124,8 @@ impl DBConn {
/// # Panics
///
/// 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
.0
.prepare(
"update task
set
@ -124,7 +138,8 @@ impl DBConn {
)
.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 if something goes wrong with the SQL
pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) {
let tx = self.0.transaction().unwrap();
pub fn swap_task_order(&mut self, task1_id: i64, task2_id: i64) -> Result<()> {
let tx = self.transaction().unwrap();
tx.execute(
"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.commit().unwrap();
Ok(())
}
/// # Panics
///
/// 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
.0
.prepare("insert or replace into app_state(key, value) values (?1, ?2)")
.unwrap();
stmt.execute((&"selected_column", column_id.to_string()))
.unwrap();
.prepare("insert or replace into app_state(key, value) values (?1, ?2)")?;
stmt.execute((&"selected_column", column_id.to_string()))?;
Ok(())
}
/// # Panics
///
/// 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
.0
.prepare("select value from app_state where key = ?1")
.unwrap();
.prepare("select value from app_state where key = ?1")?;
stmt.query_row(["selected_column"], |row| {
let value: String = row.get::<usize, String>(0).unwrap();
Ok(value.parse::<usize>().unwrap())
})
.unwrap()
}
/// # Panics
///
/// Panics if something goes wrong with the SQL
pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) {
let mut stmt = self
.0
.prepare("update kb_column set selected_task = ?2 where id = ?1")
.unwrap();
stmt.execute((column_id, task_idx)).unwrap();
pub fn set_selected_task_for_column(&self, task_idx: usize, column_id: i64) -> Result<()> {
let mut stmt = self.prepare("update kb_column set selected_task = ?2 where id = ?1")?;
stmt.execute((column_id, task_idx))?;
Ok(())
}
/// # Panics
///
/// 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
.0
.prepare("select selected_task from kb_column where key = ?1")
.unwrap();
stmt.query_row([column_id], |row| row.get(0)).unwrap()
.prepare("select selected_task from kb_column where key = ?1")?;
stmt.query_row([column_id], |row| row.get(0))
}
}

View File

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