chore: macro node

This commit is contained in:
nathan 2022-12-04 15:23:24 +08:00
parent aca9b81c79
commit 9488f42749
23 changed files with 323 additions and 571 deletions

View File

@ -4,7 +4,7 @@
use crate::event_attrs::EventEnumAttrs;
use crate::node_attrs::NodeStructAttrs;
use crate::{is_recognizable_field, pb_attrs, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs, NODE_TYPE};
use crate::{is_recognizable_field, ty_ext::*, ASTResult, PBAttrsContainer, PBStructAttrs, NODE_TYPE};
use proc_macro2::Ident;
use syn::Meta::NameValue;
use syn::{self, punctuated::Punctuated};

View File

@ -1,10 +1,8 @@
use crate::{get_event_meta_items, parse_lit_str, symbol::*, ASTResult};
use syn::{
self,
parse::{self, Parse},
Meta::{List, NameValue, Path},
Meta::{NameValue, Path},
NestedMeta::{Lit, Meta},
};

View File

@ -1,9 +1,6 @@
#[macro_use]
extern crate syn;
#[macro_use]
extern crate quote;
mod ast;
mod ctxt;
mod pb_attrs;

View File

@ -1,11 +1,8 @@
use crate::{get_node_meta_items, get_pb_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult};
use proc_macro2::{Group, Ident, Span, TokenStream, TokenTree};
use crate::{get_node_meta_items, parse_lit_into_expr_path, symbol::*, ASTAttr, ASTResult};
use quote::ToTokens;
use syn::{
self,
parse::{self, Parse},
LitStr,
Meta::{List, NameValue, Path},
self, LitStr,
Meta::NameValue,
NestedMeta::{Lit, Meta},
};
@ -14,18 +11,20 @@ pub struct NodeStructAttrs {
pub has_child: bool,
pub child_name: Option<LitStr>,
pub child_index: Option<syn::LitInt>,
get_node_value_with: Option<syn::ExprPath>,
set_node_value_with: Option<syn::ExprPath>,
pub get_node_value_with: Option<syn::ExprPath>,
pub set_node_value_with: Option<syn::ExprPath>,
pub with_children: Option<syn::ExprPath>,
}
impl NodeStructAttrs {
/// Extract out the `#[node(...)]` attributes from a struct field.
pub fn from_ast(ast_result: &ASTResult, index: usize, field: &syn::Field) -> Self {
pub fn from_ast(ast_result: &ASTResult, _index: usize, field: &syn::Field) -> Self {
let mut rename = ASTAttr::none(ast_result, RENAME_NODE);
let mut child_name = ASTAttr::none(ast_result, CHILD_NODE_NAME);
let mut child_index = ASTAttr::none(ast_result, CHILD_NODE_INDEX);
let mut get_node_value_with = ASTAttr::none(ast_result, GET_NODE_VALUE_WITH);
let mut set_node_value_with = ASTAttr::none(ast_result, SET_NODE_VALUE_WITH);
let mut with_children = ASTAttr::none(ast_result, WITH_CHILDREN);
for meta_item in field
.attrs
@ -69,6 +68,13 @@ impl NodeStructAttrs {
}
}
// Parse `#[node(with_children= "...")]`
Meta(NameValue(m)) if m.path == WITH_CHILDREN => {
if let Ok(path) = parse_lit_into_expr_path(ast_result, WITH_CHILDREN, &m.lit) {
with_children.set(&m.path, path);
}
}
Meta(meta_item) => {
let path = meta_item.path().into_token_stream().to_string().replace(' ', "");
ast_result.error_spanned_by(meta_item.path(), format!("unknown node field attribute `{}`", path));
@ -87,14 +93,7 @@ impl NodeStructAttrs {
child_name,
get_node_value_with: get_node_value_with.get(),
set_node_value_with: set_node_value_with.get(),
with_children: with_children.get(),
}
}
pub fn set_node_value_with(&self) -> Option<&syn::ExprPath> {
self.set_node_value_with.as_ref()
}
pub fn get_node_value_with(&self) -> Option<&syn::ExprPath> {
self.get_node_value_with.as_ref()
}
}

View File

@ -45,6 +45,7 @@ pub const GET_NODE_VALUE_WITH: Symbol = Symbol("get_value_with");
pub const SET_NODE_VALUE_WITH: Symbol = Symbol("set_value_with");
pub const GET_VEC_ELEMENT_WITH: Symbol = Symbol("get_element_with");
pub const GET_MUT_VEC_ELEMENT_WITH: Symbol = Symbol("get_mut_element_with");
pub const WITH_CHILDREN: Symbol = Symbol("with_children");
impl PartialEq<Symbol> for Ident {
fn eq(&self, word: &Symbol) -> bool {

View File

@ -62,7 +62,9 @@ pub fn parse_ty<'a>(ast_result: &ASTResult, ty: &'a syn::Type) -> Result<Option<
"Vec" => generate_vec_ty_info(ast_result, seg, bracketed),
"Option" => generate_option_ty_info(ast_result, ty, seg, bracketed),
_ => {
return Err(format!("Unsupported ty {}", seg.ident));
let msg = format!("Unsupported type: {}", seg.ident);
ast_result.error_spanned_by(&seg.ident, &msg);
return Err(msg);
}
}
} else {

View File

@ -1,4 +1,5 @@
use flowy_ast::EventEnumAttrs;
use quote::format_ident;
pub struct EventASTContext {
pub event: syn::Ident,

View File

@ -15,6 +15,3 @@ pub struct ProtoCache {
pub structs: Vec<String>,
pub enums: Vec<String>,
}
#[macro_use]
extern crate quote;

View File

@ -26,8 +26,9 @@ pub fn make_helper_funcs_token_stream(ast: &ASTContainer) -> TokenStream {
let struct_ident = &ast.ident;
token_streams.extend(quote! {
impl #struct_ident {
pub fn get_path(&self) -> Path {
self.tree.read().path_from_node_id(self.node_id.clone())
pub fn get_path(&self) -> Option<Path> {
let node_id = &self.node_id?;
Some(self.tree.read().path_from_node_id(node_id.clone()))
}
}
});
@ -50,7 +51,6 @@ pub fn make_alter_children_token_stream(ast_result: &ASTResult, ast: &ASTContain
}
let children_field = children_fields.first().unwrap();
let field_name = children_field.name().unwrap();
eprintln!("😄 {:?} {:?}", struct_ident, field_name);
let child_name = children_field.node_attrs.child_name.as_ref().unwrap();
let get_func_name = format_ident!("get_{}", child_name.value());
let get_mut_func_name = format_ident!("get_mut_{}", child_name.value());
@ -60,22 +60,26 @@ pub fn make_alter_children_token_stream(ast_result: &ASTResult, ast: &ASTContain
token_streams.extend(quote! {
impl #struct_ident {
pub fn #get_func_name(&self, id: &str) -> Option<&#ty> {
self.#field_name.iter().find(|element| element.id == id)
pub fn #get_func_name<T: AsRef<str>>(&self, id: T) -> Option<&#ty> {
let id = id.as_ref();
self.#field_name.iter().find(|element| element.id == id)
}
pub fn #get_mut_func_name(&mut self, id: &str) -> Option<&mut #ty> {
self.#field_name.iter_mut().find(|element| element.id == id)
pub fn #get_mut_func_name<T: AsRef<str>>(&mut self, id: T) -> Option<&mut #ty> {
let id = id.as_ref();
self.#field_name.iter_mut().find(|element| element.id == id)
}
pub fn #remove_func_name(&mut self, id: &str) {
if let Some(index) = self.#field_name.iter().position(|element| element.id == id) {
pub fn #remove_func_name<T: AsRef<str>>(&mut self, id: T) {
let id = id.as_ref();
if let Some(index) = self.#field_name.iter().position(|element| element.id == id && element.node_id.is_some()) {
let element = self.#field_name.remove(index);
let element_path = element.get_path();
let element_path = element.get_path().unwrap();
let mut write_guard = self.tree.write();
let mut nodes = vec![];
if let Some(node_data) = write_guard.get_node_data(element.node_id.clone()) {
if let Some(node_data) = element.node_id.and_then(|node_id| write_guard.get_node_data(node_id.clone())) {
nodes.push(node_data);
}
let _ = write_guard.apply_op(NodeOperation::Delete {
@ -85,18 +89,26 @@ pub fn make_alter_children_token_stream(ast_result: &ASTResult, ast: &ASTContain
}
}
pub fn #add_func_name(&mut self, value: #ty) {
pub fn #add_func_name(&mut self, mut value: #ty) -> Result<(), String> {
if self.node_id.is_none() {
return Err("The node id is empty".to_owned());
}
let mut transaction = Transaction::new();
let parent_path = self.get_path();
let parent_path = self.get_path().unwrap();
let path = parent_path.clone_with(self.#field_name.len());
let node_data = value.to_node_data();
transaction.push_operation(NodeOperation::Insert {
path,
path: path.clone(),
nodes: vec![node_data],
});
let _ = self.tree.write().apply_transaction(transaction);
let child_node_id = self.tree.read().node_id_at_path(path).unwrap();
value.node_id = Some(child_node_id);
self.#field_name.push(value);
Ok(())
}
}
});
@ -189,21 +201,24 @@ pub fn make_get_set_value_token_steam(ast: &ASTContainer) -> Option<TokenStream>
let get_value_return_ty = field.ty;
let set_value_input_ty = field.ty;
if let Some(get_value_with_fn) = field.node_attrs.get_node_value_with() {
if let Some(get_value_with_fn) = &field.node_attrs.get_node_value_with {
token_streams.extend(quote! {
impl #struct_ident {
pub fn #get_func_name(&self) -> Option<#get_value_return_ty> {
#get_value_with_fn(self.#tree.clone(), &self.node_id, #field_name_str)
let node_id = self.node_id.as_ref()?;
#get_value_with_fn(self.#tree.clone(), node_id, #field_name_str)
}
}
});
}
if let Some(set_value_with_fn) = field.node_attrs.set_node_value_with() {
if let Some(set_value_with_fn) = &field.node_attrs.set_node_value_with {
token_streams.extend(quote! {
impl #struct_ident {
pub fn #set_func_name(&self, value: #set_value_input_ty) {
let _ = #set_value_with_fn(self.#tree.clone(), &self.node_id, #field_name_str, value);
if let Some(node_id) = self.node_id.as_ref() {
let _ = #set_value_with_fn(self.#tree.clone(), node_id, #field_name_str, value);
}
}
}
});

View File

@ -1,95 +0,0 @@
use crate::client_folder::view_node::ViewNode;
use crate::client_folder::{get_attributes_str_value, set_attributes_str_value, AtomicNodeTree};
use crate::errors::CollaborateResult;
use folder_rev_model::{AppRevision, ViewRevision};
use lib_ot::core::{NodeData, NodeDataBuilder, NodeOperation, Path, Transaction};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct AppNode {
pub id: String,
tree: Arc<AtomicNodeTree>,
pub(crate) path: Path,
views: Vec<Arc<ViewNode>>,
}
impl AppNode {
pub(crate) fn from_app_revision(
transaction: &mut Transaction,
revision: AppRevision,
tree: Arc<AtomicNodeTree>,
path: Path,
) -> CollaborateResult<Self> {
let app_id = revision.id.clone();
let app_node = NodeDataBuilder::new("app")
.insert_attribute("id", revision.id)
.insert_attribute("name", revision.name)
.insert_attribute("workspace_id", revision.workspace_id)
.build();
transaction.push_operation(NodeOperation::Insert {
path: path.clone(),
nodes: vec![app_node],
});
let views = revision
.belongings
.into_iter()
.enumerate()
.map(|(index, app)| (path.clone_with(index), app))
.flat_map(
|(path, app)| match ViewNode::from_view_revision(transaction, app, tree.clone(), path) {
Ok(view_node) => Some(Arc::new(view_node)),
Err(err) => {
tracing::error!("create view node failed: {:?}", err);
None
}
},
)
.collect::<Vec<Arc<ViewNode>>>();
Ok(Self {
id: app_id,
tree,
path,
views,
})
}
pub fn get_name(&self) -> Option<String> {
get_attributes_str_value(self.tree.clone(), &self.path, "name")
}
pub fn set_name(&self, name: String) -> CollaborateResult<()> {
set_attributes_str_value(self.tree.clone(), &self.path, "name", name)
}
fn get_workspace_id(&self) -> Option<String> {
get_attributes_str_value(self.tree.clone(), &self.path, "workspace_id")
}
fn set_workspace_id(&self, workspace_id: String) -> CollaborateResult<()> {
set_attributes_str_value(self.tree.clone(), &self.path, "workspace_id", workspace_id)
}
fn get_view(&self, view_id: &str) -> Option<&Arc<ViewNode>> {
todo!()
}
fn get_mut_view(&mut self, view_id: &str) -> Option<&mut Arc<ViewNode>> {
todo!()
}
fn add_view(&mut self, revision: ViewRevision) -> CollaborateResult<()> {
let mut transaction = Transaction::new();
let path = self.path.clone_with(self.views.len());
let view_node = ViewNode::from_view_revision(&mut transaction, revision, self.tree.clone(), path)?;
let _ = self.tree.write().apply_transaction(transaction)?;
self.views.push(Arc::new(view_node));
todo!()
}
fn remove_view(&mut self, view_id: &str) {
todo!()
}
}

View File

@ -1,21 +1,53 @@
use crate::client_folder::trash_node::TrashNode;
use crate::client_folder::workspace_node::WorkspaceNode;
use crate::errors::{CollaborateError, CollaborateResult};
use folder_rev_model::{AppRevision, ViewRevision, WorkspaceRevision};
use lib_ot::core::{
AttributeEntry, AttributeHashMap, AttributeValue, Changeset, Node, NodeDataBuilder, NodeOperation, NodeTree, Path,
Transaction,
};
use flowy_derive::Node;
use lib_ot::core::NodeTree;
use lib_ot::core::*;
use parking_lot::RwLock;
use std::string::ToString;
use std::sync::Arc;
pub type AtomicNodeTree = RwLock<NodeTree>;
pub struct FolderNodePad {
tree: Arc<AtomicNodeTree>,
// name: workspaces, index of the node,
workspaces: Vec<Arc<WorkspaceNode>>,
trash: Vec<Arc<TrashNode>>,
pub tree: Arc<AtomicNodeTree>,
pub node_id: NodeId,
pub workspaces: WorkspaceList,
pub trash: TrashList,
}
#[derive(Clone, Node)]
#[node_type = "workspaces"]
pub struct WorkspaceList {
pub tree: Arc<AtomicNodeTree>,
pub node_id: Option<NodeId>,
#[node(child_name = "workspace")]
inner: Vec<WorkspaceNode>,
}
impl std::ops::Deref for WorkspaceList {
type Target = Vec<WorkspaceNode>;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl std::ops::DerefMut for WorkspaceList {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.inner
}
}
#[derive(Clone, Node)]
#[node_type = "trash"]
pub struct TrashList {
pub tree: Arc<AtomicNodeTree>,
pub node_id: Option<NodeId>,
#[node(child_name = "trash")]
inner: Vec<TrashNode>,
}
impl FolderNodePad {
@ -23,42 +55,27 @@ impl FolderNodePad {
Self::default()
}
pub fn get_workspace(&self, workspace_id: &str) -> Option<&Arc<WorkspaceNode>> {
pub fn get_workspace(&self, workspace_id: &str) -> Option<&WorkspaceNode> {
self.workspaces.iter().find(|workspace| workspace.id == workspace_id)
}
pub fn get_mut_workspace(&mut self, workspace_id: &str) -> Option<&mut Arc<WorkspaceNode>> {
pub fn get_mut_workspace(&mut self, workspace_id: &str) -> Option<&mut WorkspaceNode> {
self.workspaces
.iter_mut()
.find(|workspace| workspace.id == workspace_id)
}
pub fn remove_workspace(&mut self, workspace_id: &str) {
if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
let mut write_guard = self.tree.write();
let mut nodes = vec![];
pub fn add_workspace(&mut self, mut workspace: WorkspaceNode) {
let path = workspaces_path().clone_with(self.workspaces.len());
let op = NodeOperation::Insert {
path: path.clone(),
nodes: vec![workspace.to_node_data()],
};
self.tree.write().apply_op(op).unwrap();
if let Some(node_data) = write_guard.get_node_data_at_path(&workspace.path) {
nodes.push(node_data);
}
let _ = write_guard.apply_op(NodeOperation::Delete {
path: workspace.path.clone(),
nodes,
});
}
}
pub fn add_workspace(&mut self, revision: WorkspaceRevision) -> CollaborateResult<()> {
let mut transaction = Transaction::new();
let node = WorkspaceNode::from_workspace_revision(
&mut transaction,
revision,
self.tree.clone(),
workspaces_path().clone_with(self.workspaces.len()),
)?;
let _ = self.tree.write().apply_transaction(transaction)?;
self.workspaces.push(Arc::new(node));
Ok(())
let node_id = self.tree.read().node_id_at_path(path).unwrap();
workspace.node_id = Some(node_id);
self.workspaces.push(workspace);
}
pub fn to_json(&self, pretty: bool) -> CollaborateResult<String> {
@ -69,6 +86,49 @@ impl FolderNodePad {
}
}
impl std::default::Default for FolderNodePad {
fn default() -> Self {
let tree = Arc::new(RwLock::new(NodeTree::default()));
// Workspace
let mut workspaces = WorkspaceList {
tree: tree.clone(),
node_id: None,
inner: vec![],
};
let workspace_node = workspaces.to_node_data();
// Trash
let mut trash = TrashList {
tree: tree.clone(),
node_id: None,
inner: vec![],
};
let trash_node = trash.to_node_data();
let folder_node = NodeDataBuilder::new("folder")
.add_node_data(workspace_node)
.add_node_data(trash_node)
.build();
let operation = NodeOperation::Insert {
path: folder_path(),
nodes: vec![folder_node],
};
let _ = tree.write().apply_op(operation).unwrap();
let node_id = tree.read().node_id_at_path(folder_path()).unwrap();
workspaces.node_id = Some(tree.read().node_id_at_path(workspaces_path()).unwrap());
trash.node_id = Some(tree.read().node_id_at_path(trash_path()).unwrap());
Self {
tree,
node_id,
workspaces,
trash,
}
}
}
fn folder_path() -> Path {
vec![0].into()
}
@ -80,75 +140,3 @@ fn workspaces_path() -> Path {
fn trash_path() -> Path {
folder_path().clone_with(1)
}
pub fn get_attributes(tree: Arc<AtomicNodeTree>, path: &Path) -> Option<AttributeHashMap> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| Some(node.attributes.clone()))
}
pub fn get_attributes_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<AttributeValue> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| node.attributes.get(key).cloned())
}
pub fn get_attributes_str_value(tree: Arc<AtomicNodeTree>, path: &Path, key: &str) -> Option<String> {
tree.read()
.get_node_at_path(&path)
.and_then(|node| node.attributes.get(key).cloned())
.and_then(|value| value.str_value())
}
pub fn set_attributes_str_value(
tree: Arc<AtomicNodeTree>,
path: &Path,
key: &str,
value: String,
) -> CollaborateResult<()> {
let old_attributes = match get_attributes(tree.clone(), path) {
None => AttributeHashMap::new(),
Some(attributes) => attributes,
};
let mut new_attributes = old_attributes.clone();
new_attributes.insert(key, value);
let update_operation = NodeOperation::Update {
path: path.clone(),
changeset: Changeset::Attributes {
new: new_attributes,
old: old_attributes,
},
};
let _ = tree.write().apply_op(update_operation)?;
Ok(())
}
impl std::default::Default for FolderNodePad {
fn default() -> Self {
let workspace_node = NodeDataBuilder::new("workspaces").build();
let trash_node = NodeDataBuilder::new("trash").build();
let folder_node = NodeDataBuilder::new("folder")
.add_node_data(workspace_node)
.add_node_data(trash_node)
.build();
let operation = NodeOperation::Insert {
path: folder_path(),
nodes: vec![folder_node],
};
let mut tree = NodeTree::default();
let _ = tree.apply_op(operation).unwrap();
Self {
tree: Arc::new(RwLock::new(tree)),
workspaces: vec![],
trash: vec![],
}
}
}
pub struct TrashNode {
tree: Arc<AtomicNodeTree>,
parent_path: Path,
}

View File

@ -1,51 +0,0 @@
use crate::client_folder::workspace_node_2::WorkspaceNode2;
use crate::errors::{CollaborateError, CollaborateResult};
use flowy_derive::Node;
use lib_ot::core::NodeTree;
use lib_ot::core::*;
use parking_lot::RwLock;
use std::sync::Arc;
pub type AtomicNodeTree = RwLock<NodeTree>;
#[derive(Node)]
#[node_type = "folder"]
pub struct FolderNodePad2 {
tree: Arc<AtomicNodeTree>,
node_id: NodeId,
// name: workspaces, index of the node,
#[node(child_name = "child")]
children: Vec<Box<dyn ToNodeData>>,
}
impl FolderNodePad2 {
pub fn new() -> Self {
// let workspace_node = NodeDataBuilder::new("workspaces").build();
// let trash_node = NodeDataBuilder::new("trash").build();
// let folder_node = NodeDataBuilder::new("folder")
// .add_node_data(workspace_node)
// .add_node_data(trash_node)
// .build();
//
// let operation = NodeOperation::Insert {
// path: folder_path(),
// nodes: vec![folder_node],
// };
// let mut tree = NodeTree::default();
// let _ = tree.apply_op(operation).unwrap();
//
// Self {
// tree: Arc::new(RwLock::new(tree)),
// workspaces: vec![],
// trash: vec![],
// }
todo!()
}
pub fn to_json(&self, pretty: bool) -> CollaborateResult<String> {
self.tree
.read()
.to_json(pretty)
.map_err(|e| CollaborateError::serde().context(e))
}
}

View File

@ -1,12 +1,11 @@
mod app_node;
mod builder;
mod folder_node;
mod folder_node_2;
mod folder_pad;
mod trash_node;
mod util;
mod view_node;
mod workspace_node;
mod workspace_node_2;
pub use folder_node::*;
pub use folder_node::*;
pub use folder_pad::*;
pub use workspace_node::*;

View File

@ -0,0 +1,20 @@
use crate::client_folder::util::*;
use crate::client_folder::AtomicNodeTree;
use flowy_derive::Node;
use lib_ot::core::*;
use std::sync::Arc;
#[derive(Clone, Node)]
#[node_type = "trash"]
pub struct TrashNode {
pub tree: Arc<AtomicNodeTree>,
pub node_id: Option<NodeId>,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub name: String,
}

View File

@ -5,7 +5,7 @@ use std::sync::Arc;
pub fn get_attributes_str_value(tree: Arc<AtomicNodeTree>, node_id: &NodeId, key: &str) -> Option<String> {
tree.read()
.get_node(node_id.clone())
.get_node(*node_id)
.and_then(|node| node.attributes.get(key).cloned())
.and_then(|value| value.str_value())
}
@ -22,7 +22,7 @@ pub fn set_attributes_str_value(
};
let mut new_attributes = old_attributes.clone();
new_attributes.insert(key, value);
let path = tree.read().path_from_node_id(node_id.clone());
let path = tree.read().path_from_node_id(*node_id);
let update_operation = NodeOperation::Update {
path,
changeset: Changeset::Attributes {
@ -34,21 +34,21 @@ pub fn set_attributes_str_value(
Ok(())
}
#[allow(dead_code)]
pub fn get_attributes_int_value(tree: Arc<AtomicNodeTree>, node_id: &NodeId, key: &str) -> Option<i64> {
tree.read()
.get_node(node_id.clone())
.get_node(*node_id)
.and_then(|node| node.attributes.get(key).cloned())
.and_then(|value| value.int_value())
}
pub fn get_attributes(tree: Arc<AtomicNodeTree>, node_id: &NodeId) -> Option<AttributeHashMap> {
tree.read()
.get_node(node_id.clone())
.and_then(|node| Some(node.attributes.clone()))
tree.read().get_node(*node_id).map(|node| node.attributes.clone())
}
#[allow(dead_code)]
pub fn get_attributes_value(tree: Arc<AtomicNodeTree>, node_id: &NodeId, key: &str) -> Option<AttributeValue> {
tree.read()
.get_node(node_id.clone())
.get_node(*node_id)
.and_then(|node| node.attributes.get(key).cloned())
}

View File

@ -1,44 +0,0 @@
use crate::client_folder::AtomicNodeTree;
use crate::errors::CollaborateResult;
use folder_rev_model::ViewRevision;
use lib_ot::core::{NodeDataBuilder, NodeOperation, Path, Transaction};
use std::sync::Arc;
#[derive(Debug, Clone)]
pub struct ViewNode {
tree: Arc<AtomicNodeTree>,
path: Path,
}
impl ViewNode {
pub(crate) fn from_view_revision(
transaction: &mut Transaction,
revision: ViewRevision,
tree: Arc<AtomicNodeTree>,
path: Path,
) -> CollaborateResult<Self> {
let view_node = NodeDataBuilder::new("view")
.insert_attribute("id", revision.id)
.insert_attribute("name", revision.name)
.build();
transaction.push_operation(NodeOperation::Insert {
path: path.clone(),
nodes: vec![view_node],
});
Ok(Self { tree, path })
}
fn get_id(&self) -> &str {
todo!()
}
fn get_app_id(&self) -> &str {
todo!()
}
fn set_app_id(&self, workspace_id: String) {
todo!()
}
}

View File

@ -1,104 +1,62 @@
use crate::client_folder::app_node::AppNode;
use crate::client_folder::view_node::ViewNode;
use crate::client_folder::{get_attributes_str_value, get_attributes_value, set_attributes_str_value, AtomicNodeTree};
use crate::errors::CollaborateResult;
use folder_rev_model::{AppRevision, WorkspaceRevision};
use lib_ot::core::{AttributeValue, NodeDataBuilder, NodeOperation, Path, Transaction};
use crate::client_folder::util::*;
use crate::client_folder::AtomicNodeTree;
use flowy_derive::Node;
use lib_ot::core::*;
use std::sync::Arc;
#[derive(Debug, Clone)]
#[derive(Clone, Node)]
#[node_type = "workspace"]
pub struct WorkspaceNode {
pub(crate) id: String,
tree: Arc<AtomicNodeTree>,
pub(crate) path: Path,
apps: Vec<Arc<AppNode>>,
pub tree: Arc<AtomicNodeTree>,
pub node_id: Option<NodeId>,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub name: String,
#[node(child_name = "app")]
pub apps: Vec<AppNode>,
}
impl WorkspaceNode {
pub(crate) fn from_workspace_revision(
transaction: &mut Transaction,
revision: WorkspaceRevision,
tree: Arc<AtomicNodeTree>,
path: Path,
) -> CollaborateResult<Self> {
let workspace_id = revision.id.clone();
let workspace_node = NodeDataBuilder::new("workspace")
.insert_attribute("id", revision.id)
.insert_attribute("name", revision.name)
.build();
transaction.push_operation(NodeOperation::Insert {
path: path.clone(),
nodes: vec![workspace_node],
});
let apps = revision
.apps
.into_iter()
.enumerate()
.map(|(index, app)| (path.clone_with(index), app))
.flat_map(
|(path, app)| match AppNode::from_app_revision(transaction, app, tree.clone(), path) {
Ok(app_node) => Some(Arc::new(app_node)),
Err(err) => {
tracing::warn!("Create app node failed: {:?}", err);
None
}
},
)
.collect::<Vec<Arc<AppNode>>>();
Ok(Self {
id: workspace_id,
pub fn new(tree: Arc<AtomicNodeTree>, id: String, name: String) -> Self {
Self {
tree,
path,
apps,
})
}
pub fn get_name(&self) -> Option<String> {
get_attributes_str_value(self.tree.clone(), &self.path, "name")
}
pub fn set_name(&self, name: &str) -> CollaborateResult<()> {
set_attributes_str_value(self.tree.clone(), &self.path, "name", name.to_string())
}
pub fn get_app(&self, app_id: &str) -> Option<&Arc<AppNode>> {
self.apps.iter().find(|app| app.id == app_id)
}
pub fn get_mut_app(&mut self, app_id: &str) -> Option<&mut Arc<AppNode>> {
self.apps.iter_mut().find(|app| app.id == app_id)
}
pub fn add_app(&mut self, app: AppRevision) -> CollaborateResult<()> {
let mut transaction = Transaction::new();
let path = self.path.clone_with(self.apps.len());
let app_node = AppNode::from_app_revision(&mut transaction, app, self.tree.clone(), path.clone())?;
let _ = self.tree.write().apply_transaction(transaction);
self.apps.push(Arc::new(app_node));
Ok(())
}
pub fn remove_app(&mut self, app_id: &str) {
if let Some(index) = self.apps.iter().position(|app| app.id == app_id) {
let app = self.apps.remove(index);
let mut nodes = vec![];
let app_node = self.tree.read().get_node_data_at_path(&app.path);
debug_assert!(app_node.is_some());
if let Some(node_data) = app_node {
nodes.push(node_data);
}
let delete_operation = NodeOperation::Delete {
path: app.path.clone(),
nodes,
};
let _ = self.tree.write().apply_op(delete_operation);
node_id: None,
id,
name,
apps: vec![],
}
}
}
pub fn get_all_apps(&self) -> Vec<Arc<AppNode>> {
self.apps.clone()
#[derive(Clone, Node)]
#[node_type = "app"]
pub struct AppNode {
pub tree: Arc<AtomicNodeTree>,
pub node_id: Option<NodeId>,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub name: String,
}
impl AppNode {
pub fn new(tree: Arc<AtomicNodeTree>, id: String, name: String) -> Self {
Self {
tree,
node_id: None,
id,
name,
}
}
}

View File

@ -1,43 +0,0 @@
use crate::client_folder::util::*;
use crate::client_folder::AtomicNodeTree;
use crate::errors::CollaborateResult;
use flowy_derive::Node;
use lib_ot::core::*;
use std::sync::Arc;
#[derive(Clone, Node)]
#[node_type = "workspace"]
pub struct WorkspaceNode2 {
tree: Arc<AtomicNodeTree>,
node_id: NodeId,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
#[node(rename = "name123")]
pub name: String,
#[node(get_value_with = "get_attributes_int_value")]
pub time: i64,
#[node(child_name = "app")]
pub apps: Vec<AppNode2>,
}
#[derive(Clone, Node)]
#[node_type = "app"]
pub struct AppNode2 {
tree: Arc<AtomicNodeTree>,
node_id: NodeId,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub id: String,
#[node(get_value_with = "get_attributes_str_value")]
#[node(set_value_with = "set_attributes_str_value")]
pub name: String,
}

View File

@ -1,9 +1,8 @@
use flowy_sync::client_folder::FolderNodePad;
use folder_rev_model::WorkspaceRevision;
use flowy_sync::client_folder::{FolderNodePad, WorkspaceNode};
#[test]
fn client_folder_create_default_folder_test() {
let folder_pad = FolderNodePad::default();
let folder_pad = FolderNodePad::new();
let json = folder_pad.to_json(false).unwrap();
assert_eq!(
json,
@ -13,16 +12,9 @@ fn client_folder_create_default_folder_test() {
#[test]
fn client_folder_create_default_folder_with_workspace_test() {
let mut folder_pad = FolderNodePad::default();
let workspace = WorkspaceRevision {
id: "1".to_string(),
name: "workspace name".to_string(),
desc: "".to_string(),
apps: vec![],
modified_time: 0,
create_time: 0,
};
folder_pad.add_workspace(workspace).unwrap();
let mut folder_pad = FolderNodePad::new();
let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string());
folder_pad.workspaces.add_workspace(workspace).unwrap();
let json = folder_pad.to_json(false).unwrap();
assert_eq!(
json,
@ -37,17 +29,10 @@ fn client_folder_create_default_folder_with_workspace_test() {
#[test]
fn client_folder_delete_workspace_test() {
let mut folder_pad = FolderNodePad::default();
let workspace = WorkspaceRevision {
id: "1".to_string(),
name: "workspace name".to_string(),
desc: "".to_string(),
apps: vec![],
modified_time: 0,
create_time: 0,
};
folder_pad.add_workspace(workspace).unwrap();
folder_pad.remove_workspace("1");
let mut folder_pad = FolderNodePad::new();
let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string());
folder_pad.workspaces.add_workspace(workspace).unwrap();
folder_pad.workspaces.remove_workspace("1");
let json = folder_pad.to_json(false).unwrap();
assert_eq!(
json,
@ -57,23 +42,17 @@ fn client_folder_delete_workspace_test() {
#[test]
fn client_folder_update_workspace_name_test() {
let mut folder_pad = FolderNodePad::default();
let workspace = WorkspaceRevision {
id: "1".to_string(),
name: "workspace name".to_string(),
desc: "".to_string(),
apps: vec![],
modified_time: 0,
create_time: 0,
};
folder_pad.add_workspace(workspace).unwrap();
let mut folder_pad = FolderNodePad::new();
let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string());
folder_pad.workspaces.add_workspace(workspace).unwrap();
folder_pad
.get_workspace("1")
.workspaces
.get_mut_workspace("1")
.unwrap()
.set_name("My first workspace")
.unwrap();
.set_name("my first workspace".to_string());
assert_eq!(
folder_pad.get_workspace("1").unwrap().get_name().unwrap(),
"My first workspace"
folder_pad.workspaces.get_workspace("1").unwrap().get_name().unwrap(),
"my first workspace"
);
}

View File

@ -1,14 +1,18 @@
use flowy_sync::client_folder::FolderNodePad;
use folder_rev_model::{AppRevision, WorkspaceRevision};
use std::sync::Arc;
use flowy_sync::client_folder::{AppNode, FolderNodePad, WorkspaceNode};
use folder_rev_model::AppRevision;
use lib_ot::core::Path;
pub enum FolderNodePadScript {
CreateWorkspace { id: String, name: String },
DeleteWorkspace { id: String },
AssertPathOfWorkspace { id: String, expected_path: Path },
AssertNumberOfWorkspace { expected: usize },
CreateApp { id: String, name: String },
DeleteApp { id: String },
UpdateApp { id: String, name: String },
AssertApp { id: String, expected: Option<AppRevision> },
AssertAppContent { id: String, name: String },
AssertNumberOfApps { expected: usize },
// AssertNumberOfApps { expected: usize },
}
pub struct FolderNodePadTest {
@ -18,15 +22,8 @@ pub struct FolderNodePadTest {
impl FolderNodePadTest {
pub fn new() -> FolderNodePadTest {
let mut folder_pad = FolderNodePad::default();
let workspace = WorkspaceRevision {
id: "1".to_string(),
name: "workspace name".to_string(),
desc: "".to_string(),
apps: vec![],
modified_time: 0,
create_time: 0,
};
let _ = folder_pad.add_workspace(workspace).unwrap();
let workspace = WorkspaceNode::new(folder_pad.tree.clone(), "1".to_string(), "workspace name".to_string());
let _ = folder_pad.workspaces.add_workspace(workspace).unwrap();
Self { folder_pad }
}
@ -38,32 +35,34 @@ impl FolderNodePadTest {
pub fn run_script(&mut self, script: FolderNodePadScript) {
match script {
FolderNodePadScript::CreateWorkspace { id, name } => {
let workspace = WorkspaceNode::new(self.folder_pad.tree.clone(), id, name);
self.folder_pad.workspaces.add_workspace(workspace).unwrap();
}
FolderNodePadScript::DeleteWorkspace { id } => {
self.folder_pad.workspaces.remove_workspace(id);
}
FolderNodePadScript::AssertPathOfWorkspace { id, expected_path } => {
let workspace_node: &WorkspaceNode = self.folder_pad.workspaces.get_workspace(id).unwrap();
let node_id = workspace_node.node_id.unwrap();
let path = self.folder_pad.tree.read().path_from_node_id(node_id);
assert_eq!(path, expected_path);
}
FolderNodePadScript::AssertNumberOfWorkspace { expected } => {
assert_eq!(self.folder_pad.workspaces.len(), expected);
}
FolderNodePadScript::CreateApp { id, name } => {
let revision = AppRevision {
id,
workspace_id: "1".to_string(),
name,
desc: "".to_string(),
belongings: vec![],
version: 0,
modified_time: 0,
create_time: 0,
};
let app_node = AppNode::new(self.folder_pad.tree.clone(), id, name);
let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
let workspace_node = Arc::make_mut(workspace_node);
let _ = workspace_node.add_app(revision).unwrap();
let _ = workspace_node.add_app(app_node).unwrap();
}
FolderNodePadScript::DeleteApp { id } => {
let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
let workspace_node = Arc::make_mut(workspace_node);
workspace_node.remove_app(&id);
}
FolderNodePadScript::UpdateApp { id, name } => {
let workspace_node = self.folder_pad.get_mut_workspace("1").unwrap();
let workspace_node = Arc::make_mut(workspace_node);
let app_node = Arc::make_mut(workspace_node.get_mut_app(&id).unwrap());
app_node.set_name(name).unwrap();
workspace_node.get_mut_app(&id).unwrap().set_name(name);
}
FolderNodePadScript::AssertApp { id, expected } => {
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
@ -73,7 +72,7 @@ impl FolderNodePadTest {
Some(expected_app) => {
let app_node = app.unwrap();
assert_eq!(expected_app.name, app_node.get_name().unwrap());
assert_eq!(expected_app.id, app_node.id);
assert_eq!(expected_app.id, app_node.get_id().unwrap());
}
}
}
@ -81,11 +80,10 @@ impl FolderNodePadTest {
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
let app = workspace_node.get_app(&id).unwrap();
assert_eq!(app.get_name().unwrap(), name)
}
FolderNodePadScript::AssertNumberOfApps { expected } => {
let workspace_node = self.folder_pad.get_workspace("1").unwrap();
assert_eq!(workspace_node.get_all_apps().len(), expected);
}
} // FolderNodePadScript::AssertNumberOfApps { expected } => {
// let workspace_node = self.folder_pad.get_workspace("1").unwrap();
// assert_eq!(workspace_node.apps.len(), expected);
// }
}
}
}

View File

@ -1,6 +1,39 @@
use crate::client_folder::script::FolderNodePadScript::*;
use crate::client_folder::script::FolderNodePadTest;
use flowy_sync::client_folder::FolderNodePad;
#[test]
fn client_folder_create_multi_workspaces_test() {
let mut test = FolderNodePadTest::new();
test.run_scripts(vec![
AssertPathOfWorkspace {
id: "1".to_string(),
expected_path: vec![0, 0, 0].into(),
},
CreateWorkspace {
id: "a".to_string(),
name: "workspace a".to_string(),
},
AssertPathOfWorkspace {
id: "a".to_string(),
expected_path: vec![0, 0, 1].into(),
},
CreateWorkspace {
id: "b".to_string(),
name: "workspace b".to_string(),
},
AssertPathOfWorkspace {
id: "b".to_string(),
expected_path: vec![0, 0, 2].into(),
},
AssertNumberOfWorkspace { expected: 3 },
// The path of the workspace 'b' will be changed after deleting the 'a' workspace.
DeleteWorkspace { id: "a".to_string() },
AssertPathOfWorkspace {
id: "b".to_string(),
expected_path: vec![0, 0, 1].into(),
},
]);
}
#[test]
fn client_folder_create_app_test() {

View File

@ -61,8 +61,8 @@ where
predicate: impl Fn(&Arc<T>) -> bool,
) -> Option<Arc<T>> {
let objects = self.get_objects(field_id, field_type)?;
let index = objects.iter().position(|object| predicate(object))?;
objects.get(index).map(|object| object.clone())
let index = objects.iter().position(predicate)?;
objects.get(index).cloned()
}
pub fn get_mut_object(
@ -72,7 +72,7 @@ where
predicate: impl Fn(&Arc<T>) -> bool,
) -> Option<&mut Arc<T>> {
let objects = self.get_mut_objects(field_id, field_type)?;
let index = objects.iter().position(|object| predicate(object))?;
let index = objects.iter().position(predicate)?;
objects.get_mut(index)
}

View File

@ -196,7 +196,7 @@ impl NodeTree {
path.push(counter);
current_node = parent_node;
}
path.reverse();
Path(path)
}