1use crate::scanner::{FileNode, TreeStats};
6use anyhow::Result;
7use serde_json;
8use std::io::Write;
9use std::path::Path;
10
11use super::{Formatter, StreamingFormatter};
12
13pub struct SseFormatter {
14 event_id: u64,
15}
16
17impl Default for SseFormatter {
18 fn default() -> Self {
19 Self::new()
20 }
21}
22
23impl SseFormatter {
24 pub fn new() -> Self {
25 Self { event_id: 0 }
26 }
27
28 fn next_event_id(&mut self) -> u64 {
29 self.event_id += 1;
30 self.event_id
31 }
32
33 fn write_event(
34 &self,
35 writer: &mut dyn Write,
36 event_type: &str,
37 data: &serde_json::Value,
38 id: u64,
39 ) -> Result<()> {
40 writeln!(writer, "id: {}", id)?;
41 writeln!(writer, "event: {}", event_type)?;
42 writeln!(writer, "data: {}", serde_json::to_string(data)?)?;
43 writeln!(writer)?; writer.flush()?;
45 Ok(())
46 }
47}
48
49impl Formatter for SseFormatter {
50 fn format(
51 &self,
52 writer: &mut dyn Write,
53 nodes: &[FileNode],
54 stats: &TreeStats,
55 root_path: &Path,
56 ) -> Result<()> {
57 let mut formatter = SseFormatter::new();
58
59 let scan_event = serde_json::json!({
61 "type": "scan_complete",
62 "path": root_path.display().to_string(),
63 "stats": {
64 "total_files": stats.total_files,
65 "total_dirs": stats.total_dirs,
66 "total_size": stats.total_size,
67 }
68 });
69 let id = formatter.next_event_id();
70 formatter.write_event(writer, "scan", &scan_event, id)?;
71
72 for node in nodes {
74 let node_event = serde_json::json!({
75 "type": "node",
76 "node": {
77 "name": node.path.file_name().unwrap_or(node.path.as_os_str()).to_string_lossy(),
78 "path": node.path.display().to_string(),
79 "is_dir": node.is_dir,
80 "size": node.size,
81 "depth": node.depth,
82 }
83 });
84 let id = formatter.next_event_id();
85 formatter.write_event(writer, "node", &node_event, id)?;
86 }
87
88 let complete_event = serde_json::json!({
90 "type": "format_complete",
91 "node_count": nodes.len(),
92 });
93 let id = formatter.next_event_id();
94 formatter.write_event(writer, "complete", &complete_event, id)?;
95
96 Ok(())
97 }
98}
99
100impl StreamingFormatter for SseFormatter {
101 fn start_stream(&self, writer: &mut dyn Write, root_path: &Path) -> Result<()> {
102 writeln!(writer, "HTTP/1.1 200 OK")?;
104 writeln!(writer, "Content-Type: text/event-stream")?;
105 writeln!(writer, "Cache-Control: no-cache")?;
106 writeln!(writer, "Connection: keep-alive")?;
107 writeln!(writer, "Access-Control-Allow-Origin: *")?;
108 writeln!(writer)?; let mut formatter = SseFormatter::new();
112 let init_event = serde_json::json!({
113 "type": "stream_start",
114 "path": root_path.display().to_string(),
115 "timestamp": chrono::Utc::now().to_rfc3339(),
116 });
117 let id = formatter.next_event_id();
118 formatter.write_event(writer, "init", &init_event, id)?;
119
120 Ok(())
121 }
122
123 fn format_node(
124 &self,
125 writer: &mut dyn Write,
126 node: &FileNode,
127 _root_path: &Path,
128 ) -> Result<()> {
129 let mut formatter = SseFormatter::new();
130
131 let node_event = serde_json::json!({
132 "type": "node_discovered",
133 "node": {
134 "name": node.path.file_name().unwrap_or(node.path.as_os_str()).to_string_lossy(),
135 "path": node.path.display().to_string(),
136 "is_dir": node.is_dir,
137 "size": node.size,
138 "depth": node.depth,
139 "permissions": format!("{:o}", node.permissions),
140 "modified": chrono::DateTime::<chrono::Utc>::from(node.modified).to_rfc3339(),
141 }
142 });
143
144 let id = formatter.next_event_id();
145 formatter.write_event(writer, "node", &node_event, id)?;
146 Ok(())
147 }
148
149 fn end_stream(
150 &self,
151 writer: &mut dyn Write,
152 stats: &TreeStats,
153 root_path: &Path,
154 ) -> Result<()> {
155 let mut formatter = SseFormatter::new();
156
157 let stats_event = serde_json::json!({
159 "type": "stream_complete",
160 "path": root_path.display().to_string(),
161 "stats": {
162 "total_files": stats.total_files,
163 "total_dirs": stats.total_dirs,
164 "total_size": stats.total_size,
165 },
166 "timestamp": chrono::Utc::now().to_rfc3339(),
167 });
168
169 let id = formatter.next_event_id();
170 formatter.write_event(writer, "complete", &stats_event, id)?;
171
172 let close_event = serde_json::json!({
174 "type": "stream_close",
175 "reason": "scan_complete",
176 });
177 let id = formatter.next_event_id();
178 formatter.write_event(writer, "close", &close_event, id)?;
179
180 Ok(())
181 }
182}