use std::path::Path;
use serde::{Deserialize, Serialize};
use crate::{Channel, Error, Result, Task};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TasksFile {
#[serde(default)]
pub meta: TasksMeta,
#[serde(default)]
pub tasks: Vec<TaskEntry>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct TasksMeta {
#[serde(default)]
pub description: String,
#[serde(default)]
pub default_agent: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TaskEntry {
#[serde(default = "generate_short_id")]
pub id: String,
pub description: String,
#[serde(default)]
pub agent: Option<String>,
#[serde(default = "default_status")]
pub status: String,
#[serde(default)]
pub tags: Vec<String>,
#[serde(default)]
pub parent: Option<String>,
}
fn generate_short_id() -> String {
use std::time::{SystemTime, UNIX_EPOCH};
let ts = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_millis();
format!("task-{}", ts % 100000)
}
fn default_status() -> String {
"pending".to_string()
}
pub fn init_tasks_file(town_path: &Path) -> Result<()> {
let tasks_file = town_path.join("tasks.toml");
let template = TasksFile {
meta: TasksMeta {
description: "Task plan for this project".to_string(),
default_agent: None,
},
tasks: vec![TaskEntry {
id: "example-1".to_string(),
description: "Example task - replace with your own".to_string(),
agent: None,
status: "pending".to_string(),
tags: vec!["example".to_string()],
parent: None,
}],
};
let content =
toml::to_string_pretty(&template).map_err(|e| Error::Io(std::io::Error::other(e)))?;
std::fs::write(&tasks_file, content)?;
Ok(())
}
pub fn load_tasks_file(town_path: &Path) -> Result<TasksFile> {
let tasks_file = town_path.join("tasks.toml");
let content = std::fs::read_to_string(&tasks_file)?;
let tasks: TasksFile =
toml::from_str(&content).map_err(|e| Error::Io(std::io::Error::other(e)))?;
Ok(tasks)
}
pub fn save_tasks_file(town_path: &Path, tasks: &TasksFile) -> Result<()> {
let tasks_file = town_path.join("tasks.toml");
let content = toml::to_string_pretty(tasks).map_err(|e| Error::Io(std::io::Error::other(e)))?;
std::fs::write(&tasks_file, content)?;
Ok(())
}
pub async fn push_tasks_to_redis(town_path: &Path, channel: &Channel) -> Result<usize> {
let tasks_file = load_tasks_file(town_path)?;
let mut count = 0;
for entry in &tasks_file.tasks {
let mut task = Task::new(&entry.description);
let mut all_tags: Vec<String> = entry.tags.clone();
all_tags.push(format!("plan:{}", entry.id));
task = task.with_tags(all_tags);
match entry.status.as_str() {
"completed" => task.complete("Completed via sync"),
"failed" => task.fail("Failed via sync"),
_ => {}
}
channel.set_task(&task).await?;
count += 1;
}
Ok(count)
}
pub async fn pull_tasks_from_redis(town_path: &Path, _channel: &Channel) -> Result<usize> {
let tasks_file = town_path.join("tasks.toml");
if !tasks_file.exists() {
init_tasks_file(town_path)?;
}
Ok(0)
}