1use crate::{
14 markup::{markup_to_node, DeserializationError},
15 model::Tile,
16 DbView,
17};
18use assemblage_db::{
19 broadcast::BroadcastId,
20 data::{Child, Id, Layout, Node},
21 Db,
22};
23use assemblage_kv::storage::{self, PlatformStorage, Storage};
24use log::info;
25use serde::{Deserialize, Serialize};
26use std::{
27 convert::{TryFrom, TryInto},
28 rc::Rc,
29};
30
31#[cfg(target_arch = "wasm32")]
32use wasm_bindgen::prelude::*;
33#[cfg(target_arch = "wasm32")]
34use wasm_bindgen_futures::future_to_promise;
35
36#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
39pub struct DbContainer {
40 wrapped: Rc<Db<PlatformStorage>>,
41}
42
43#[cfg(target_arch = "wasm32")]
45#[wasm_bindgen]
46pub async fn open(name: String) -> Result<DbContainer, JsValue> {
47 #[cfg(feature = "console_error_panic_hook")]
48 console_error_panic_hook::set_once();
49 let _ignored = console_log::init();
50 info!("Opening AssemblageDB \"{}\"", &name);
51 let storage = storage::open(&name).await?;
52 Ok(DbContainer {
53 wrapped: Rc::new(Db::open(storage).await?),
54 })
55}
56
57#[cfg(not(target_arch = "wasm32"))]
59pub async fn open(name: String) -> crate::Result<DbContainer> {
60 let _ignored = env_logger::try_init();
61 info!("Opening AssemblageDB \"{}\"", &name);
62 let storage = storage::open(&name).await?;
63 Ok(DbContainer {
64 wrapped: Rc::new(Db::open(storage).await?),
65 })
66}
67
68#[cfg(target_arch = "wasm32")]
69#[wasm_bindgen]
70impl DbContainer {
71 pub fn refresh(&self, id: String) -> js_sys::Promise {
79 let db = Rc::clone(&self.wrapped);
80 future_to_promise(async move {
81 let tile = refresh(db, id).await?;
82 Ok(JsValue::from_serde(&tile).unwrap())
83 })
84 }
85
86 pub fn sync(&self, id: Option<String>, tile: JsValue) -> js_sys::Promise {
89 let db = Rc::clone(&self.wrapped);
90 future_to_promise(async move {
91 let tile: Result<Vec<SyncedSection>, serde_json::Error> = tile.into_serde();
92 match tile {
93 Ok(tile) => {
94 let updated_tile = sync(db, id, tile).await?;
95 Ok(JsValue::from_serde(&updated_tile).unwrap())
96 }
97 Err(e) => Err(JsValue::from_str(&format!("{}", e))),
98 }
99 })
100 }
101
102 pub fn broadcast(&self, id: String) -> js_sys::Promise {
108 let db = Rc::clone(&self.wrapped);
109 future_to_promise(async move {
110 let updated_tile = broadcast(db, id).await?;
111 Ok(JsValue::from_serde(&updated_tile).unwrap())
112 })
113 }
114
115 pub fn fetch(&self, id: String) -> js_sys::Promise {
118 let db = Rc::clone(&self.wrapped);
119 future_to_promise(async move {
120 match id.as_str().try_into() {
121 Ok(id) => {
122 let mut current = db.current().await;
123 current.fetch_broadcast(&BroadcastId::from(id)).await?;
124 let tile = current.tile(id).await?;
125 current.commit().await?;
126 Ok(JsValue::from_serde(&tile).unwrap())
127 }
128 Err(_) => {
129 let e = BroadcastError::InvalidId(id);
130 Err(JsValue::from_str(&format!("{:?}", e)))
131 }
132 }
133 })
134 }
135}
136
137#[cfg(not(target_arch = "wasm32"))]
138impl DbContainer {
139 pub async fn refresh(&self, id: String) -> Result<Tile, RefreshError> {
147 let db = Rc::clone(&self.wrapped);
148 Ok(refresh(db, id).await?)
149 }
150
151 pub async fn sync(
154 &self,
155 id: Option<String>,
156 tile: Vec<SyncedSection>,
157 ) -> Result<Tile, SyncError> {
158 let db = Rc::clone(&self.wrapped);
159 Ok(sync(db, id, tile).await?)
160 }
161
162 pub async fn broadcast(&self, id: String) -> Result<Tile, BroadcastError> {
168 let db = Rc::clone(&self.wrapped);
169 Ok(broadcast(db, id).await?)
170 }
171
172 pub async fn fetch(&self, id: String) -> Result<Tile, BroadcastError> {
175 match id.as_str().try_into() {
176 Ok(id) => {
177 let db = Rc::clone(&self.wrapped);
178 let mut current = db.current().await;
179 current.fetch_broadcast(&BroadcastId::from(id)).await?;
180 let tile = current.tile(id).await?;
181 current.commit().await?;
182 Ok(tile)
183 }
184 Err(_) => Err(BroadcastError::InvalidId(id)),
185 }
186 }
187}
188
189#[derive(Debug)]
192pub enum RefreshError {
193 InvalidBroadcastId(String),
195 InvalidId(String),
197 ViewError(crate::Error),
199}
200
201impl<E: Into<crate::Error>> From<E> for RefreshError {
202 fn from(e: E) -> Self {
203 Self::ViewError(e.into())
204 }
205}
206
207#[cfg(target_arch = "wasm32")]
208impl From<RefreshError> for JsValue {
209 fn from(e: RefreshError) -> Self {
210 JsValue::from_str(&format!("{:?}", e))
211 }
212}
213
214async fn refresh<S: Storage>(db: Rc<Db<S>>, id: String) -> Result<Tile, RefreshError> {
215 if id.starts_with("broadcast:") {
216 let id = id.replace("broadcast:", "");
217 match Id::try_from(id.as_str()) {
218 Ok(id) => {
219 let mut current = db.current().await;
220 let tile = current.tile_from_broadcast(&BroadcastId::from(id)).await?;
221 current.commit().await?;
222 Ok(tile)
223 }
224 Err(_) => Err(RefreshError::InvalidBroadcastId(id)),
225 }
226 } else {
227 match id.as_str().try_into() {
228 Ok(id) => {
229 let current = db.current().await;
230 let tile = current.tile(id).await?;
231 current.commit().await?;
232 Ok(tile)
233 }
234 Err(_) => Err(RefreshError::InvalidId(id)),
235 }
236 }
237}
238
239#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
241#[serde(tag = "type")]
242pub enum SyncedSection {
243 Existing {
245 id: Id,
247 },
248 Linked {
250 id: Id,
252 },
253 Edited {
255 blocks: Vec<SyncedSubsection>,
257 },
258}
259
260#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
262#[serde(tag = "type")]
263pub enum SyncedSubsection {
264 Text {
266 markup: String,
268 },
269}
270
271#[derive(Debug)]
274pub enum SyncError {
275 ExternalId(String),
278 InvalidId(String),
280 DeserializationError(DeserializationError),
282 DbError(assemblage_db::Error),
284 ViewError(crate::Error),
286}
287
288impl<E: Into<assemblage_db::Error>> From<E> for SyncError {
289 fn from(e: E) -> Self {
290 Self::DbError(e.into())
291 }
292}
293
294impl From<DeserializationError> for SyncError {
295 fn from(e: DeserializationError) -> Self {
296 Self::DeserializationError(e)
297 }
298}
299
300impl From<crate::Error> for SyncError {
301 fn from(e: crate::Error) -> Self {
302 Self::ViewError(e)
303 }
304}
305
306#[cfg(target_arch = "wasm32")]
307impl From<SyncError> for JsValue {
308 fn from(e: SyncError) -> Self {
309 JsValue::from_str(&format!("{:?}", e))
310 }
311}
312
313async fn sync<S>(
314 db: Rc<Db<S>>,
315 id: Option<String>,
316 s: Vec<SyncedSection>,
317) -> Result<Tile, SyncError>
318where
319 S: Storage,
320{
321 let id = match id {
322 None => None,
323 Some(id) => match id.as_str().try_into() {
324 Ok(id) => Some(id),
325 Err(_) => return Err(SyncError::InvalidId(id)),
326 },
327 };
328 let mut db = db.current().await;
329 let mut children = Vec::with_capacity(s.len());
330 for section in s.iter() {
331 children.push(match section {
332 SyncedSection::Existing { id } => Child::Lazy(*id),
333 SyncedSection::Linked { id } => Child::Eager(Node::list(Layout::Chain, vec![*id])),
334 SyncedSection::Edited { blocks } => {
335 let mut children = Vec::with_capacity(blocks.len());
336 for b in blocks.iter() {
337 match b {
338 SyncedSubsection::Text { markup } => {
339 children.push(markup_to_node(markup)?);
340 }
341 }
342 }
343 Child::Eager(Node::list(Layout::Page, children))
344 }
345 })
346 }
347 let replacement = Node::list(Layout::Page, children);
348 let id = match id {
349 None => db.add(replacement).await?,
350 Some(id) => {
351 db.swap(id, replacement).await?;
352 id
353 }
354 };
355 let result = db.tile(id).await?;
356 db.update_broadcasts(id).await?;
357 db.commit().await?;
358 Ok(result)
359}
360
361#[derive(Debug)]
364pub enum BroadcastError {
365 InvalidId(String),
367 DbError(assemblage_db::Error),
369 ViewError(crate::Error),
371}
372
373impl<E: Into<assemblage_db::Error>> From<E> for BroadcastError {
374 fn from(e: E) -> Self {
375 Self::DbError(e.into())
376 }
377}
378
379impl From<crate::Error> for BroadcastError {
380 fn from(e: crate::Error) -> Self {
381 Self::ViewError(e)
382 }
383}
384
385#[cfg(target_arch = "wasm32")]
386impl From<BroadcastError> for JsValue {
387 fn from(e: BroadcastError) -> Self {
388 JsValue::from_str(&format!("{:?}", e))
389 }
390}
391
392async fn broadcast<S>(db: Rc<Db<S>>, id: String) -> Result<Tile, BroadcastError>
393where
394 S: Storage,
395{
396 let id = match id.as_str().try_into() {
397 Ok(id) => id,
398 Err(_) => return Err(BroadcastError::InvalidId(id)),
399 };
400 let mut db = db.current().await;
401 db.publish_broadcast(id).await?;
402 let result = db.tile(id).await?;
403 db.commit().await?;
404 Ok(result)
405}