use std::fs::DirBuilder;
use std::result::Result;
use std::str::FromStr;
use rusqlite::{params, Connection, Row, NO_PARAMS};
use crate::model::{Priority, SmartDate, Task};
use time::OffsetDateTime;
fn open_connection() -> Connection {
let database_file_path = super::database_file_path();
let database_file_dir = database_file_path.parent().unwrap();
if !database_file_dir.exists() {
DirBuilder::new().create(database_file_dir).unwrap();
}
Connection::open(&database_file_path).unwrap()
}
pub fn update(task: &Task) -> Result<Task, String> {
let conn = open_connection();
conn.execute_named(
"UPDATE todo SET title = :title, list = :list, done = :done, today = :today, priority = :priority, completed_at = :completed_at, due_date = :due_date WHERE id = :id",
&[
(":title", &task.title),
(":list", &task.list.as_str()),
(":priority", &task.priority.to_string()),
(":done", if task.done { &1 } else { &0 }),
(":today", &task.today),
(":id", &task.id),
(":completed_at", &task.completed_at.map_or(0, |date| date.timestamp())),
(":due_date", &task.due_date.as_ref().map_or("".to_string(), |date| date.format("%F"))),
])
.map_err(|db_err|db_err.to_string())
.and_then(|_| get(task.id))
}
pub fn add(task: &Task) -> Result<Task, String> {
let conn = open_connection();
conn.execute_named(
"INSERT INTO todo (title, list, priority, today, due_date, created_at) VALUES (:title, :list, :priority, :today, :due_date, :created_at)",
&[
(":title", &task.title),
(":list", &task.list.as_str()),
(":priority", &task.priority.to_string()),
(":today", &task.today),
(":created_at", &task.created_at.timestamp()),
(":due_date", &task.due_date.as_ref().map_or("".to_string(), |date| date.format("%F"))),
],
)
.unwrap();
let result = conn
.prepare("SELECT * FROM todo ORDER BY id DESC LIMIT 1")
.and_then(|mut stmt| stmt.query_row(NO_PARAMS, |row| Ok(map_to_task(row))));
match result {
Ok(task) => Ok(task),
Err(err) => {
eprintln!("{}", err.to_string());
Err(String::from("Failed to add task"))
}
}
}
pub fn get(task_id: u32) -> Result<Task, String> {
let conn = open_connection();
let mut stmt = conn.prepare("SELECT * FROM todo WHERE id = (?1)").unwrap();
let todo = stmt.query_row(params![task_id], |row| Ok(map_to_task(row)));
match todo {
Ok(task) => Ok(task),
_ => Err(format!("Task with id '{}' not found", task_id)),
}
}
pub fn delete(task_id: u32) -> Result<u32, String> {
let conn = open_connection();
let mut stmt = conn.prepare("DELETE FROM todo WHERE id = (?1)").unwrap();
match stmt.execute(params![task_id]) {
Ok(1) => Ok(task_id),
Ok(0) => Err(format!("Task with id '{}' not found", task_id)),
_ => Err(format!("Failed to delete task '{}'", task_id)),
}
}
pub fn get_all() -> Result<Vec<Task>, String> {
let conn = open_connection();
let mut stmt = conn.prepare("SELECT * FROM todo").unwrap();
let todo_iter = stmt
.query_map(NO_PARAMS, |row| Ok(map_to_task(row)))
.unwrap();
let mut result: Vec<Task> = Vec::new();
for task in todo_iter {
result.push(task.unwrap());
}
Ok(result)
}
fn map_to_task(row: &Row) -> Task {
let due_date = row
.get("due_date")
.ok()
.map(|due_date: String| {
if due_date.is_empty() {
None
} else {
SmartDate::from_str(due_date.as_str()).ok()
}
})
.flatten();
let completed_time = row
.get("completed_at")
.ok()
.map(|completed_at| {
if completed_at == 0 {
None
} else {
Some(OffsetDateTime::from_unix_timestamp(completed_at))
}
})
.flatten();
Task::create(
row.get("id").unwrap(),
row.get("title").unwrap(),
row.get("done").ok().map_or(false, |done: u8| done == 1),
row.get("list").unwrap(),
row.get("priority")
.ok()
.map_or(Priority::default(), |priority: String| {
Priority::from_str(&priority).unwrap()
}),
row.get("today").unwrap(),
row.get("created_at")
.ok()
.map(OffsetDateTime::from_unix_timestamp)
.unwrap(),
completed_time,
due_date,
)
}