use crate::{
markup::{markup_to_node, DeserializationError},
model::Tile,
DbView,
};
use assemblage_db::{
broadcast::BroadcastId,
data::{Child, Id, Layout, Node},
Db,
};
use assemblage_kv::storage::{self, PlatformStorage, Storage};
use log::info;
use serde::{Deserialize, Serialize};
use std::{
convert::{TryFrom, TryInto},
rc::Rc,
};
#[cfg(target_arch = "wasm32")]
use wasm_bindgen::prelude::*;
#[cfg(target_arch = "wasm32")]
use wasm_bindgen_futures::future_to_promise;
#[cfg_attr(target_arch = "wasm32", wasm_bindgen)]
pub struct DbContainer {
wrapped: Rc<Db<PlatformStorage>>,
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
pub async fn open(name: String) -> Result<DbContainer, JsValue> {
#[cfg(feature = "console_error_panic_hook")]
console_error_panic_hook::set_once();
let _ignored = console_log::init();
info!("Opening AssemblageDB \"{}\"", &name);
let storage = storage::open(&name).await?;
Ok(DbContainer {
wrapped: Rc::new(Db::open(storage).await?),
})
}
#[cfg(not(target_arch = "wasm32"))]
pub async fn open(name: String) -> crate::Result<DbContainer> {
let _ignored = env_logger::try_init();
info!("Opening AssemblageDB \"{}\"", &name);
let storage = storage::open(&name).await?;
Ok(DbContainer {
wrapped: Rc::new(Db::open(storage).await?),
})
}
#[cfg(target_arch = "wasm32")]
#[wasm_bindgen]
impl DbContainer {
pub fn refresh(&self, id: String) -> js_sys::Promise {
let db = Rc::clone(&self.wrapped);
future_to_promise(async move {
let tile = refresh(db, id).await?;
Ok(JsValue::from_serde(&tile).unwrap())
})
}
pub fn sync(&self, id: Option<String>, tile: JsValue) -> js_sys::Promise {
let db = Rc::clone(&self.wrapped);
future_to_promise(async move {
let tile: Result<Vec<SyncedSection>, serde_json::Error> = tile.into_serde();
match tile {
Ok(tile) => {
let updated_tile = sync(db, id, tile).await?;
Ok(JsValue::from_serde(&updated_tile).unwrap())
}
Err(e) => Err(JsValue::from_str(&format!("{}", e))),
}
})
}
pub fn broadcast(&self, id: String) -> js_sys::Promise {
let db = Rc::clone(&self.wrapped);
future_to_promise(async move {
let updated_tile = broadcast(db, id).await?;
Ok(JsValue::from_serde(&updated_tile).unwrap())
})
}
pub fn fetch(&self, id: String) -> js_sys::Promise {
let db = Rc::clone(&self.wrapped);
future_to_promise(async move {
match id.as_str().try_into() {
Ok(id) => {
let mut current = db.current().await;
current.fetch_broadcast(&BroadcastId::from(id)).await?;
let tile = current.tile(id).await?;
current.commit().await?;
Ok(JsValue::from_serde(&tile).unwrap())
}
Err(_) => {
let e = BroadcastError::InvalidId(id);
Err(JsValue::from_str(&format!("{:?}", e)))
}
}
})
}
}
#[cfg(not(target_arch = "wasm32"))]
impl DbContainer {
pub async fn refresh(&self, id: String) -> Result<Tile, RefreshError> {
let db = Rc::clone(&self.wrapped);
Ok(refresh(db, id).await?)
}
pub async fn sync(
&self,
id: Option<String>,
tile: Vec<SyncedSection>,
) -> Result<Tile, SyncError> {
let db = Rc::clone(&self.wrapped);
Ok(sync(db, id, tile).await?)
}
pub async fn broadcast(&self, id: String) -> Result<Tile, BroadcastError> {
let db = Rc::clone(&self.wrapped);
Ok(broadcast(db, id).await?)
}
pub async fn fetch(&self, id: String) -> Result<Tile, BroadcastError> {
match id.as_str().try_into() {
Ok(id) => {
let db = Rc::clone(&self.wrapped);
let mut current = db.current().await;
current.fetch_broadcast(&BroadcastId::from(id)).await?;
let tile = current.tile(id).await?;
current.commit().await?;
Ok(tile)
}
Err(_) => Err(BroadcastError::InvalidId(id)),
}
}
}
#[derive(Debug)]
pub enum RefreshError {
InvalidBroadcastId(String),
InvalidId(String),
ViewError(crate::Error),
}
impl<E: Into<crate::Error>> From<E> for RefreshError {
fn from(e: E) -> Self {
Self::ViewError(e.into())
}
}
#[cfg(target_arch = "wasm32")]
impl From<RefreshError> for JsValue {
fn from(e: RefreshError) -> Self {
JsValue::from_str(&format!("{:?}", e))
}
}
async fn refresh<S: Storage>(db: Rc<Db<S>>, id: String) -> Result<Tile, RefreshError> {
if id.starts_with("broadcast:") {
let id = id.replace("broadcast:", "");
match Id::try_from(id.as_str()) {
Ok(id) => {
let mut current = db.current().await;
let tile = current.tile_from_broadcast(&BroadcastId::from(id)).await?;
current.commit().await?;
Ok(tile)
}
Err(_) => Err(RefreshError::InvalidBroadcastId(id)),
}
} else {
match id.as_str().try_into() {
Ok(id) => {
let current = db.current().await;
let tile = current.tile(id).await?;
current.commit().await?;
Ok(tile)
}
Err(_) => Err(RefreshError::InvalidId(id)),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(tag = "type")]
pub enum SyncedSection {
Existing {
id: Id,
},
Linked {
id: Id,
},
Edited {
blocks: Vec<SyncedSubsection>,
},
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
#[serde(tag = "type")]
pub enum SyncedSubsection {
Text {
markup: String,
},
}
#[derive(Debug)]
pub enum SyncError {
ExternalId(String),
InvalidId(String),
DeserializationError(DeserializationError),
DbError(assemblage_db::Error),
ViewError(crate::Error),
}
impl<E: Into<assemblage_db::Error>> From<E> for SyncError {
fn from(e: E) -> Self {
Self::DbError(e.into())
}
}
impl From<DeserializationError> for SyncError {
fn from(e: DeserializationError) -> Self {
Self::DeserializationError(e)
}
}
impl From<crate::Error> for SyncError {
fn from(e: crate::Error) -> Self {
Self::ViewError(e)
}
}
#[cfg(target_arch = "wasm32")]
impl From<SyncError> for JsValue {
fn from(e: SyncError) -> Self {
JsValue::from_str(&format!("{:?}", e))
}
}
async fn sync<S>(
db: Rc<Db<S>>,
id: Option<String>,
s: Vec<SyncedSection>,
) -> Result<Tile, SyncError>
where
S: Storage,
{
let id = match id {
None => None,
Some(id) => match id.as_str().try_into() {
Ok(id) => Some(id),
Err(_) => return Err(SyncError::InvalidId(id)),
},
};
let mut db = db.current().await;
let mut children = Vec::with_capacity(s.len());
for section in s.iter() {
children.push(match section {
SyncedSection::Existing { id } => Child::Lazy(*id),
SyncedSection::Linked { id } => Child::Eager(Node::list(Layout::Chain, vec![*id])),
SyncedSection::Edited { blocks } => {
let mut children = Vec::with_capacity(blocks.len());
for b in blocks.iter() {
match b {
SyncedSubsection::Text { markup } => {
children.push(markup_to_node(markup)?);
}
}
}
Child::Eager(Node::list(Layout::Page, children))
}
})
}
let replacement = Node::list(Layout::Page, children);
let id = match id {
None => db.add(replacement).await?,
Some(id) => {
db.swap(id, replacement).await?;
id
}
};
let result = db.tile(id).await?;
db.update_broadcasts(id).await?;
db.commit().await?;
Ok(result)
}
#[derive(Debug)]
pub enum BroadcastError {
InvalidId(String),
DbError(assemblage_db::Error),
ViewError(crate::Error),
}
impl<E: Into<assemblage_db::Error>> From<E> for BroadcastError {
fn from(e: E) -> Self {
Self::DbError(e.into())
}
}
impl From<crate::Error> for BroadcastError {
fn from(e: crate::Error) -> Self {
Self::ViewError(e)
}
}
#[cfg(target_arch = "wasm32")]
impl From<BroadcastError> for JsValue {
fn from(e: BroadcastError) -> Self {
JsValue::from_str(&format!("{:?}", e))
}
}
async fn broadcast<S>(db: Rc<Db<S>>, id: String) -> Result<Tile, BroadcastError>
where
S: Storage,
{
let id = match id.as_str().try_into() {
Ok(id) => id,
Err(_) => return Err(BroadcastError::InvalidId(id)),
};
let mut db = db.current().await;
db.publish_broadcast(id).await?;
let result = db.tile(id).await?;
db.commit().await?;
Ok(result)
}