use std::path::Path;
use grumpydb::GrumpyDb;
use uuid::Uuid;
use super::task::Task;
pub struct TaskStore {
db: GrumpyDb,
}
impl TaskStore {
pub fn open(path: &Path) -> Result<Self, String> {
let db = GrumpyDb::open(path).map_err(|e| format!("Failed to open database: {e}"))?;
Ok(Self { db })
}
pub fn add_task(&mut self, task: Task) -> Result<Uuid, String> {
let id = task.id;
let value = task.to_value();
self.db
.insert(id, value)
.map_err(|e| format!("Failed to add task: {e}"))?;
Ok(id)
}
pub fn get_task(&mut self, id: &Uuid) -> Result<Option<Task>, String> {
let value = self
.db
.get(id)
.map_err(|e| format!("Failed to get task: {e}"))?;
Ok(value.and_then(|v| Task::from_value(*id, &v)))
}
pub fn update_task(&mut self, task: &Task) -> Result<(), String> {
let value = task.to_value();
self.db
.update(&task.id, value)
.map_err(|e| format!("Failed to update task: {e}"))
}
pub fn set_task_done(&mut self, id: &Uuid, done: bool) -> Result<(), String> {
let mut task = self
.get_task(id)?
.ok_or_else(|| format!("Task {id} not found"))?;
task.done = done;
self.update_task(&task)
}
pub fn delete_task(&mut self, id: &Uuid) -> Result<(), String> {
self.db
.delete(id)
.map_err(|e| format!("Failed to delete task: {e}"))
}
pub fn list_all_tasks(&mut self) -> Result<Vec<Task>, String> {
let entries = self
.db
.scan(..)
.map_err(|e| format!("Failed to list tasks: {e}"))?;
Ok(entries
.iter()
.filter_map(|(key, value)| Task::from_value(*key, value))
.collect())
}
pub fn list_by_status(&mut self, done: bool) -> Result<Vec<Task>, String> {
let all = self.list_all_tasks()?;
Ok(all.into_iter().filter(|t| t.done == done).collect())
}
pub fn stats(&mut self) -> Result<(usize, usize, usize), String> {
let all = self.list_all_tasks()?;
let total = all.len();
let done = all.iter().filter(|t| t.done).count();
let pending = total - done;
Ok((total, done, pending))
}
pub fn flush(&mut self) -> Result<(), String> {
self.db.flush().map_err(|e| format!("Failed to flush: {e}"))
}
pub fn close(self) -> Result<(), String> {
self.db
.close()
.map_err(|e| format!("Failed to close database: {e}"))
}
pub fn pool_stats(&self) -> (u64, u64, usize, usize) {
self.db.pool_stats()
}
pub fn compact(&mut self) -> Result<grumpydb::CompactResult, String> {
self.db
.compact()
.map_err(|e| format!("Failed to compact: {e}"))
}
pub fn document_count(&self) -> u64 {
self.db.document_count()
}
pub fn export_tasks(&mut self) -> Result<String, String> {
let tasks = self.list_all_tasks()?;
let mut output = String::new();
for task in &tasks {
let desc = task.description.as_deref().unwrap_or("");
let tags = task.tags.join(",");
output.push_str(&format!(
"{}|{}|{}|{}|{}|{}\n",
task.id, task.title, desc, task.done, task.created_at, tags
));
}
Ok(output)
}
pub fn import_tasks(&mut self, data: &str) -> Result<usize, String> {
let mut count = 0;
for line in data.lines() {
let line = line.trim();
if line.is_empty() {
continue;
}
let parts: Vec<&str> = line.splitn(6, '|').collect();
if parts.len() < 5 {
continue; }
let id = uuid::Uuid::parse_str(parts[0]).map_err(|e| format!("Invalid UUID: {e}"))?;
let title = parts[1].to_string();
let description = if parts[2].is_empty() {
None
} else {
Some(parts[2].to_string())
};
let done = parts[3] == "true";
let created_at: i64 = parts[4].parse().unwrap_or(0);
let tags: Vec<String> = if parts.len() > 5 && !parts[5].is_empty() {
parts[5].split(',').map(String::from).collect()
} else {
Vec::new()
};
let task = super::task::Task {
id,
title,
description,
done,
created_at,
tags,
};
let value = task.to_value();
match self.db.insert(id, value) {
Ok(()) => count += 1,
Err(grumpydb::GrumpyError::DuplicateKey(_)) => {
}
Err(e) => return Err(format!("Import failed: {e}")),
}
}
Ok(count)
}
}