codetether_agent/session/tasks/
log.rs1use anyhow::{Context, Result};
4use std::path::{Path, PathBuf};
5use tokio::fs::{self, OpenOptions};
6use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
7
8use super::event::TaskEvent;
9use super::path::task_log_path;
10
11pub struct TaskLog {
17 path: PathBuf,
18}
19
20impl TaskLog {
21 pub fn for_session(session_id: &str) -> Result<Self> {
23 Ok(Self {
24 path: task_log_path(session_id)?,
25 })
26 }
27
28 pub fn at(path: impl Into<PathBuf>) -> Self {
30 Self { path: path.into() }
31 }
32
33 pub fn path(&self) -> &Path {
35 &self.path
36 }
37
38 pub async fn append(&self, event: &TaskEvent) -> Result<()> {
40 if let Some(parent) = self.path.parent() {
41 fs::create_dir_all(parent).await.ok();
42 }
43 let mut line = serde_json::to_string(event).context("serialize task event")?;
44 line.push('\n');
45 let mut f = OpenOptions::new()
46 .create(true)
47 .append(true)
48 .open(&self.path)
49 .await
50 .with_context(|| format!("open {} for append", self.path.display()))?;
51 f.write_all(line.as_bytes()).await?;
52 f.flush().await?;
53 Ok(())
54 }
55
56 pub async fn read_all(&self) -> Result<Vec<TaskEvent>> {
58 if !self.path.exists() {
59 return Ok(Vec::new());
60 }
61 let file = fs::File::open(&self.path).await?;
62 let mut out = Vec::new();
63 let mut lines = BufReader::new(file).lines();
64 while let Some(line) = lines.next_line().await? {
65 if line.trim().is_empty() {
66 continue;
67 }
68 if let Ok(event) = serde_json::from_str::<TaskEvent>(&line) {
69 out.push(event);
70 }
71 }
72 Ok(out)
73 }
74
75 pub fn read_all_blocking(&self) -> Result<Vec<TaskEvent>> {
80 if !self.path.exists() {
81 return Ok(Vec::new());
82 }
83 let content = std::fs::read_to_string(&self.path)?;
84 let mut out = Vec::new();
85 for line in content.lines() {
86 if line.trim().is_empty() {
87 continue;
88 }
89 if let Ok(event) = serde_json::from_str::<TaskEvent>(line) {
90 out.push(event);
91 }
92 }
93 Ok(out)
94 }
95
96 pub fn append_blocking(&self, event: &TaskEvent) -> Result<()> {
99 use std::io::Write;
100 if let Some(parent) = self.path.parent() {
101 std::fs::create_dir_all(parent).ok();
102 }
103 let mut line = serde_json::to_string(event).context("serialize task event")?;
104 line.push('\n');
105 let mut f = std::fs::OpenOptions::new()
106 .create(true)
107 .append(true)
108 .open(&self.path)
109 .with_context(|| format!("open {} for append", self.path.display()))?;
110 f.write_all(line.as_bytes())?;
111 Ok(())
112 }
113}