1use crate::app::AppState;
4use crate::document::{RefContent, autosave};
5use automerge::hydrate;
6use automerge::transaction::Transactable;
7use futures_util::stream::StreamExt;
8use samod::DocHandle;
9use serde_json::Value;
10use uuid::Uuid;
11
12fn insert_value_into_map<'a>(
14 tx: &mut automerge::transaction::Transaction<'a>,
15 parent: &automerge::ObjId,
16 key: &str,
17 value: &Value,
18) -> Result<(), automerge::AutomergeError> {
19 match value {
20 Value::String(s) => {
21 let text_id = tx.put_object(parent, key, automerge::ObjType::Text)?;
23 tx.splice_text(&text_id, 0, 0, s.as_str())?;
24 }
25 Value::Number(n) => {
26 if let Some(i) = n.as_i64() {
27 tx.put(parent, key, i)?;
28 } else if let Some(f) = n.as_f64() {
29 tx.put(parent, key, f)?;
30 }
31 }
32 Value::Bool(b) => {
33 tx.put(parent, key, *b)?;
34 }
35 Value::Null => {
36 tx.put(parent, key, ())?;
37 }
38 Value::Object(map) => {
39 let obj_id = tx.put_object(parent, key, automerge::ObjType::Map)?;
40 for (nested_key, nested_val) in map {
41 insert_value_into_map(tx, &obj_id, nested_key.as_str(), nested_val)?;
42 }
43 }
44 Value::Array(arr) => {
45 let list_id = tx.put_object(parent, key, automerge::ObjType::List)?;
46 for (i, item) in arr.iter().enumerate() {
47 insert_value_into_list(tx, &list_id, i, item)?;
48 }
49 }
50 }
51 Ok(())
52}
53
54fn insert_value_into_list<'a>(
56 tx: &mut automerge::transaction::Transaction<'a>,
57 parent: &automerge::ObjId,
58 index: usize,
59 value: &Value,
60) -> Result<(), automerge::AutomergeError> {
61 match value {
62 Value::String(s) => {
63 let text_id = tx.insert_object(parent, index, automerge::ObjType::Text)?;
65 tx.splice_text(&text_id, 0, 0, s.as_str())?;
66 }
67 Value::Number(n) => {
68 if let Some(i) = n.as_i64() {
69 tx.insert(parent, index, i)?;
70 } else if let Some(f) = n.as_f64() {
71 tx.insert(parent, index, f)?;
72 }
73 }
74 Value::Bool(b) => {
75 tx.insert(parent, index, *b)?;
76 }
77 Value::Null => {
78 tx.insert(parent, index, ())?;
79 }
80 Value::Object(map) => {
81 let obj_id = tx.insert_object(parent, index, automerge::ObjType::Map)?;
82 for (nested_key, nested_val) in map {
83 insert_value_into_map(tx, &obj_id, nested_key.as_str(), nested_val)?;
84 }
85 }
86 Value::Array(arr) => {
87 let list_id = tx.insert_object(parent, index, automerge::ObjType::List)?;
88 for (i, item) in arr.iter().enumerate() {
89 insert_value_into_list(tx, &list_id, i, item)?;
90 }
91 }
92 }
93 Ok(())
94}
95
96pub(crate) fn populate_automerge_from_json<'a>(
98 tx: &mut automerge::transaction::Transaction<'a>,
99 obj_id: automerge::ObjId,
100 value: &Value,
101) -> Result<(), automerge::AutomergeError> {
102 let Value::Object(map) = value else {
103 let value_type = match value {
104 Value::Null => "Null",
105 Value::Bool(_) => "Bool",
106 Value::Number(_) => "Number",
107 Value::String(_) => "String",
108 Value::Array(_) => "Array",
109 Value::Object(_) => unreachable!(),
110 };
111
112 return Err(automerge::AutomergeError::InvalidValueType {
113 expected: "Object".to_string(),
114 unexpected: format!("{} as document root", value_type),
115 });
116 };
117
118 for (key, val) in map {
119 insert_value_into_map(tx, &obj_id, key.as_str(), val)?;
120 }
121
122 Ok(())
123}
124
125pub(crate) fn hydrate_to_json(value: &hydrate::Value) -> Value {
127 match value {
128 hydrate::Value::Scalar(s) => scalar_to_json(s),
129 hydrate::Value::Map(m) => {
130 let mut map = serde_json::Map::new();
131 for (key, map_value) in m.iter() {
132 map.insert(key.to_string(), hydrate_to_json(&map_value.value));
133 }
134 Value::Object(map)
135 }
136 hydrate::Value::List(l) => {
137 Value::Array(l.iter().map(|list_value| hydrate_to_json(&list_value.value)).collect())
138 }
139 hydrate::Value::Text(t) => Value::String(t.to_string()),
140 }
141}
142
143fn scalar_to_json(s: &automerge::ScalarValue) -> Value {
144 use automerge::ScalarValue;
145 match s {
146 ScalarValue::Bytes(b) => {
147 Value::Array(b.iter().map(|v| Value::Number((*v).into())).collect())
148 }
149 ScalarValue::Str(s) => Value::String(s.to_string()),
150 ScalarValue::Int(i) => Value::Number((*i).into()),
151 ScalarValue::Uint(u) => Value::Number((*u).into()),
152 ScalarValue::F64(f) => {
153 serde_json::Number::from_f64(*f).map(Value::Number).unwrap_or(Value::Null)
154 }
155 ScalarValue::Counter(c) => Value::Number(i64::from(c).into()),
156 ScalarValue::Timestamp(t) => Value::Number((*t).into()),
157 ScalarValue::Boolean(b) => Value::Bool(*b),
158 ScalarValue::Null => Value::Null,
159 ScalarValue::Unknown { type_code, bytes } => Value::Object(serde_json::Map::from_iter([
160 ("type_code".to_string(), Value::Number((*type_code).into())),
161 (
162 "bytes".to_string(),
163 Value::Array(bytes.iter().map(|b| Value::Number((*b).into())).collect()),
164 ),
165 ])),
166 }
167}
168
169pub(crate) async fn ensure_autosave_listener(state: AppState, ref_id: Uuid, doc_handle: DocHandle) {
171 let listeners = state.active_listeners.read().await;
172 if listeners.contains(&ref_id) {
173 return;
174 }
175
176 drop(listeners);
178
179 let mut listeners = state.active_listeners.write().await;
180 listeners.insert(ref_id);
181
182 tokio::spawn({
183 let state = state.clone();
184 async move {
185 let mut changes = doc_handle.changes();
186
187 while (changes.next().await).is_some() {
188 let cloned_doc = doc_handle.with_document(|doc| doc.clone());
189 let hydrated = cloned_doc.hydrate(None);
190 let content = hydrate_to_json(&hydrated);
191
192 let data = RefContent { ref_id, content };
193 if let Err(e) = autosave(state.clone(), data).await {
194 tracing::error!("Autosave failed for ref {}: {:?}", ref_id, e);
195 }
196 }
197
198 state.active_listeners.write().await.remove(&ref_id);
199 tracing::error!("Autosave listener stopped for ref {}", ref_id);
200 }
201 });
202}