Feat/view map database (#1885)

* refactor: rename structs

* chore: read database id from view

* chore: fix open database error because of create a database view for database id

* chore: fix tests

* chore: rename datbase id to view id in flutter

* refactor: move grid and board to database view folder

* refactor: rename functions

* refactor: move calender to datbase view folder

* refactor: rename app_flowy to appflowy_flutter

* chore: reanming

* chore: fix freeze gen

* chore: remove todos

* refactor: view process events

* chore: add link database test

* chore: just open view if there is opened database
This commit is contained in:
Nathan.fooo
2023-02-26 16:27:17 +08:00
committed by GitHub
parent 6877607c5e
commit 61fd608200
2213 changed files with 43935 additions and 45507 deletions

View File

@ -6,7 +6,7 @@ pub struct ViewIdentify(pub String);
impl ViewIdentify {
pub fn parse(s: String) -> Result<ViewIdentify, ErrorCode> {
if s.trim().is_empty() {
return Err(ErrorCode::ViewIdInvalid);
return Err(ErrorCode::ViewIdIsInvalid);
}
Ok(Self(s))

View File

@ -8,6 +8,7 @@ use crate::{
};
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use folder_model::{gen_view_id, ViewDataFormatRevision, ViewLayoutTypeRevision, ViewRevision};
use std::collections::HashMap;
use std::convert::TryInto;
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
@ -159,13 +160,13 @@ pub struct CreateViewPayloadPB {
pub thumbnail: Option<String>,
#[pb(index = 5)]
pub data_format: ViewDataFormatPB,
#[pb(index = 6)]
pub layout: ViewLayoutTypePB,
#[pb(index = 7)]
#[pb(index = 6)]
pub initial_data: Vec<u8>,
#[pb(index = 7)]
pub ext: HashMap<String, String>,
}
#[derive(Debug, Clone)]
@ -178,6 +179,7 @@ pub struct CreateViewParams {
pub layout: ViewLayoutTypePB,
pub view_id: String,
pub initial_data: Vec<u8>,
pub ext: HashMap<String, String>,
}
impl TryInto<CreateViewParams> for CreateViewPayloadPB {
@ -191,20 +193,31 @@ impl TryInto<CreateViewParams> for CreateViewPayloadPB {
None => "".to_string(),
Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0,
};
let data_format = data_format_from_layout(&self.layout);
Ok(CreateViewParams {
belong_to_id,
name,
desc: self.desc,
data_format: self.data_format,
data_format,
layout: self.layout,
thumbnail,
view_id,
initial_data: self.initial_data,
ext: self.ext,
})
}
}
pub fn data_format_from_layout(layout: &ViewLayoutTypePB) -> ViewDataFormatPB {
match layout {
ViewLayoutTypePB::Document => ViewDataFormatPB::NodeFormat,
ViewLayoutTypePB::Grid => ViewDataFormatPB::DatabaseFormat,
ViewLayoutTypePB::Board => ViewDataFormatPB::DatabaseFormat,
ViewLayoutTypePB::Calendar => ViewDataFormatPB::DatabaseFormat,
}
}
#[derive(Default, ProtoBuf, Clone, Debug)]
pub struct ViewIdPB {
#[pb(index = 1)]

View File

@ -13,6 +13,9 @@ mod notification;
pub mod protobuf;
mod util;
#[cfg(feature = "flowy_unit_test")]
pub mod test_helper;
pub mod prelude {
pub use crate::{errors::*, event_map::*};
}

View File

@ -70,7 +70,7 @@ pub struct FolderManager {
pub(crate) view_controller: Arc<ViewController>,
pub(crate) trash_controller: Arc<TrashController>,
web_socket: Arc<dyn RevisionWebSocket>,
folder_editor: Arc<TokioRwLock<Option<Arc<FolderEditor>>>>,
pub(crate) folder_editor: Arc<TokioRwLock<Option<Arc<FolderEditor>>>>,
}
impl FolderManager {
@ -181,7 +181,8 @@ impl FolderManager {
let rev_persistence = RevisionPersistence::new(user_id, object_id, disk_cache, configuration);
let rev_compactor = FolderRevisionMergeable();
let snapshot_object_id = format!("folder:{}", object_id);
const FOLDER_SP_PREFIX: &str = "folder";
let snapshot_object_id = format!("{}:{}", FOLDER_SP_PREFIX, object_id);
let snapshot_persistence =
SQLiteFolderRevisionSnapshotPersistence::new(&snapshot_object_id, pool);
let rev_manager = RevisionManager::new(
@ -257,7 +258,7 @@ impl DefaultFolderBuilder {
let _ = view_controller.set_latest_view(&view.id);
let layout_type = ViewLayoutTypePB::from(view.layout.clone());
view_controller
.create_view(&view.id, view_data_type, layout_type, view_data)
.create_view(&view.id, &view.name, view_data_type, layout_type, view_data)
.await?;
}
}
@ -275,41 +276,38 @@ impl DefaultFolderBuilder {
}
}
#[cfg(feature = "flowy_unit_test")]
impl FolderManager {
pub async fn folder_editor(&self) -> Arc<FolderEditor> {
self.folder_editor.read().await.clone().unwrap()
}
}
pub trait ViewDataProcessor {
fn create_view(
&self,
user_id: &str,
view_id: &str,
layout: ViewLayoutTypePB,
view_data: Bytes,
) -> FutureResult<(), FlowyError>;
/// Closes the view and releases the resources that this view has in
/// the backend
fn close_view(&self, view_id: &str) -> FutureResult<(), FlowyError>;
/// Gets the data of the this view.
/// For example, the data can be used to duplicate the view.
fn get_view_data(&self, view: &ViewPB) -> FutureResult<Bytes, FlowyError>;
fn create_default_view(
/// Create a view with the pre-defined data.
/// For example, the initial data of the grid/calendar/kanban board when
/// you create a new view.
fn create_view_with_build_in_data(
&self,
user_id: &str,
view_id: &str,
name: &str,
layout: ViewLayoutTypePB,
data_format: ViewDataFormatPB,
) -> FutureResult<Bytes, FlowyError>;
ext: HashMap<String, String>,
) -> FutureResult<(), FlowyError>;
fn create_view_with_data(
/// Create a view with custom data
fn create_view_with_custom_data(
&self,
user_id: &str,
view_id: &str,
name: &str,
data: Vec<u8>,
layout: ViewLayoutTypePB,
) -> FutureResult<Bytes, FlowyError>;
ext: HashMap<String, String>,
) -> FutureResult<(), FlowyError>;
fn data_types(&self) -> Vec<ViewDataFormatPB>;
}

View File

@ -6,7 +6,7 @@ use crate::{
services::{AppController, TrashController, ViewController},
};
use folder_model::TrashRevision;
use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use std::{convert::TryInto, sync::Arc};
pub(crate) async fn create_app_handler(
@ -16,7 +16,7 @@ pub(crate) async fn create_app_handler(
let params: CreateAppParams = data.into_inner().try_into()?;
let detail = controller.create_app_from_params(params).await?;
data_result(detail)
data_result_ok(detail)
}
pub(crate) async fn delete_app_handler(
@ -55,7 +55,7 @@ pub(crate) async fn read_app_handler(
let params: AppIdPB = data.into_inner();
if let Some(mut app_rev) = app_controller.read_app(params.clone()).await? {
app_rev.belongings = view_controller.read_views_belong_to(&params.value).await?;
data_result(app_rev.into())
data_result_ok(app_rev.into())
} else {
Err(FlowyError::record_not_found())
}

View File

@ -3,7 +3,7 @@ use crate::{
errors::FlowyError,
services::TrashController,
};
use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use std::sync::Arc;
#[tracing::instrument(level = "debug", skip(controller), err)]
@ -11,7 +11,7 @@ pub(crate) async fn read_trash_handler(
controller: AFPluginState<Arc<TrashController>>,
) -> DataResult<RepeatedTrashPB, FlowyError> {
let repeated_trash = controller.read_trash().await?;
data_result(repeated_trash)
data_result_ok(repeated_trash)
}
#[tracing::instrument(level = "debug", skip(identifier, controller), err)]

View File

@ -18,11 +18,13 @@ use bytes::Bytes;
use flowy_sqlite::kv::KV;
use folder_model::{gen_view_id, ViewRevision};
use futures::{FutureExt, StreamExt};
use lib_infra::util::timestamp;
use std::collections::HashMap;
use std::{collections::HashSet, sync::Arc};
const LATEST_VIEW_ID: &str = "latest_view_id";
pub(crate) struct ViewController {
pub struct ViewController {
user: Arc<dyn WorkspaceUser>,
cloud_service: Arc<dyn FolderCouldServiceV1>,
persistence: Arc<FolderPersistence>,
@ -55,43 +57,61 @@ impl ViewController {
#[tracing::instrument(level = "trace", skip(self, params), fields(name = %params.name), err)]
pub(crate) async fn create_view_from_params(
&self,
mut params: CreateViewParams,
params: CreateViewParams,
) -> Result<ViewRevision, FlowyError> {
let processor = self.get_data_processor(params.data_format.clone())?;
let user_id = self.user.user_id()?;
if params.initial_data.is_empty() {
tracing::trace!("Create view with build-in data");
let view_data = processor
.create_default_view(
&user_id,
&params.view_id,
params.layout.clone(),
params.data_format.clone(),
)
.await?;
params.initial_data = view_data.to_vec();
} else {
tracing::trace!("Create view with view data");
let view_data = processor
.create_view_with_data(
&user_id,
&params.view_id,
params.initial_data.clone(),
params.layout.clone(),
)
.await?;
self
.create_view(
&params.view_id,
params.data_format.clone(),
params.layout.clone(),
view_data,
)
.await?;
};
match params.initial_data.is_empty() {
true => {
tracing::trace!("Create view with build-in data");
processor
.create_view_with_build_in_data(
&user_id,
&params.view_id,
&params.name,
params.layout.clone(),
params.data_format.clone(),
params.ext.clone(),
)
.await?;
},
false => {
tracing::trace!("Create view with view data");
processor
.create_view_with_custom_data(
&user_id,
&params.view_id,
&params.name,
params.initial_data.clone(),
params.layout.clone(),
params.ext.clone(),
)
.await?;
},
}
let trash_controller = self.trash_controller.clone();
let view_rev = self
.persistence
.begin_transaction(|transaction| {
let time = timestamp();
let view_rev = ViewRevision::new(
params.view_id,
params.belong_to_id,
params.name,
params.desc,
params.data_format.into(),
params.layout.into(),
time,
time,
);
let belong_to_id = view_rev.app_id.clone();
transaction.create_view(view_rev.clone())?;
notify_views_changed(&belong_to_id, trash_controller, &transaction)?;
Ok(view_rev)
})
.await?;
let view_rev = self.create_view_on_server(params).await?;
self.create_view_on_local(view_rev.clone()).await?;
Ok(view_rev)
}
@ -99,6 +119,7 @@ impl ViewController {
pub(crate) async fn create_view(
&self,
view_id: &str,
name: &str,
data_type: ViewDataFormatPB,
layout_type: ViewLayoutTypePB,
view_data: Bytes,
@ -109,27 +130,18 @@ impl ViewController {
let user_id = self.user.user_id()?;
let processor = self.get_data_processor(data_type)?;
processor
.create_view(&user_id, view_id, layout_type, view_data)
.create_view_with_custom_data(
&user_id,
view_id,
name,
view_data.to_vec(),
layout_type,
HashMap::default(),
)
.await?;
Ok(())
}
pub(crate) async fn create_view_on_local(
&self,
view_rev: ViewRevision,
) -> Result<(), FlowyError> {
let trash_controller = self.trash_controller.clone();
self
.persistence
.begin_transaction(|transaction| {
let belong_to_id = view_rev.app_id.clone();
transaction.create_view(view_rev)?;
notify_views_changed(&belong_to_id, trash_controller, &transaction)?;
Ok(())
})
.await
}
#[tracing::instrument(level = "debug", skip(self, view_id), err)]
pub(crate) async fn read_view(&self, view_id: &str) -> Result<ViewRevision, FlowyError> {
let view_rev = self
@ -252,6 +264,7 @@ impl ViewController {
layout: view_rev.layout.into(),
initial_data: view_data.to_vec(),
view_id: gen_view_id(),
ext: Default::default(),
};
let _ = self.create_view_from_params(duplicate_params).await?;
@ -316,16 +329,6 @@ impl ViewController {
}
impl ViewController {
#[tracing::instrument(level = "debug", skip(self, params), err)]
async fn create_view_on_server(
&self,
params: CreateViewParams,
) -> Result<ViewRevision, FlowyError> {
let token = self.user.token()?;
let view_rev = self.cloud_service.create_view(&token, params).await?;
Ok(view_rev)
}
#[tracing::instrument(level = "debug", skip(self), err)]
fn update_view_on_server(&self, params: UpdateViewParams) -> Result<(), FlowyError> {
let token = self.user.token()?;

View File

@ -13,7 +13,7 @@ use crate::{
services::{TrashController, ViewController},
};
use folder_model::TrashRevision;
use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use std::{convert::TryInto, sync::Arc};
pub(crate) async fn create_view_handler(
@ -22,7 +22,7 @@ pub(crate) async fn create_view_handler(
) -> DataResult<ViewPB, FlowyError> {
let params: CreateViewParams = data.into_inner().try_into()?;
let view_rev = controller.create_view_from_params(params).await?;
data_result(view_rev.into())
data_result_ok(view_rev.into())
}
pub(crate) async fn read_view_handler(
@ -31,7 +31,7 @@ pub(crate) async fn read_view_handler(
) -> DataResult<ViewPB, FlowyError> {
let view_id: ViewIdPB = data.into_inner();
let view_rev = controller.read_view(&view_id.value).await?;
data_result(view_rev.into())
data_result_ok(view_rev.into())
}
#[tracing::instrument(level = "debug", skip(data, controller), err)]

View File

@ -8,7 +8,7 @@ use crate::{
manager::FolderManager,
services::{get_current_workspace, read_workspace_apps, WorkspaceController},
};
use lib_dispatch::prelude::{data_result, AFPluginData, AFPluginState, DataResult};
use lib_dispatch::prelude::{data_result_ok, AFPluginData, AFPluginState, DataResult};
use std::{convert::TryInto, sync::Arc};
#[tracing::instrument(level = "debug", skip(data, controller), err)]
@ -19,7 +19,7 @@ pub(crate) async fn create_workspace_handler(
let controller = controller.get_ref().clone();
let params: CreateWorkspaceParams = data.into_inner().try_into()?;
let workspace_rev = controller.create_workspace_from_params(params).await?;
data_result(workspace_rev.into())
data_result_ok(workspace_rev.into())
}
#[tracing::instrument(level = "debug", skip(controller), err)]
@ -33,7 +33,7 @@ pub(crate) async fn read_workspace_apps_handler(
.map(|app_rev| app_rev.into())
.collect();
let repeated_app = RepeatedAppPB { items };
data_result(repeated_app)
data_result_ok(repeated_app)
}
#[tracing::instrument(level = "debug", skip(data, controller), err)]
@ -43,7 +43,7 @@ pub(crate) async fn open_workspace_handler(
) -> DataResult<WorkspacePB, FlowyError> {
let params: WorkspaceIdPB = data.into_inner();
let workspaces = controller.open_workspace(params).await?;
data_result(workspaces)
data_result_ok(workspaces)
}
#[tracing::instrument(level = "debug", skip(data, folder), err)]
@ -71,7 +71,7 @@ pub(crate) async fn read_workspaces_handler(
Ok(workspaces)
})
.await?;
data_result(workspaces)
data_result_ok(workspaces)
}
#[tracing::instrument(level = "debug", skip(folder), err)]
@ -99,5 +99,5 @@ pub async fn read_cur_workspace_handler(
workspace,
latest_view,
};
data_result(setting)
data_result_ok(setting)
}

View File

@ -0,0 +1,63 @@
use crate::entities::{data_format_from_layout, CreateViewParams, ViewLayoutTypePB};
use crate::manager::FolderManager;
use crate::services::folder_editor::FolderEditor;
use folder_model::gen_view_id;
use std::collections::HashMap;
use std::sync::Arc;
#[cfg(feature = "flowy_unit_test")]
impl FolderManager {
pub async fn folder_editor(&self) -> Arc<FolderEditor> {
self.folder_editor.read().await.clone().unwrap()
}
pub async fn create_test_grid_view(
&self,
app_id: &str,
name: &str,
ext: HashMap<String, String>,
) -> String {
self
.create_test_view(app_id, name, ViewLayoutTypePB::Grid, ext)
.await
}
pub async fn create_test_board_view(
&self,
app_id: &str,
name: &str,
ext: HashMap<String, String>,
) -> String {
self
.create_test_view(app_id, name, ViewLayoutTypePB::Board, ext)
.await
}
async fn create_test_view(
&self,
app_id: &str,
name: &str,
layout: ViewLayoutTypePB,
ext: HashMap<String, String>,
) -> String {
let view_id = gen_view_id();
let data_format = data_format_from_layout(&layout);
let params = CreateViewParams {
belong_to_id: app_id.to_string(),
name: name.to_string(),
desc: "".to_string(),
thumbnail: "".to_string(),
data_format,
layout,
view_id: view_id.clone(),
initial_data: vec![],
ext,
};
self
.view_controller
.create_view_from_params(params)
.await
.unwrap();
view_id
}
}

View File

@ -98,7 +98,6 @@ impl FolderTest {
&app.id,
"Folder View",
"Folder test view",
ViewDataFormatPB::DeltaFormat,
ViewLayoutTypePB::Document,
)
.await;
@ -189,7 +188,7 @@ impl FolderTest {
ViewDataFormatPB::NodeFormat => ViewLayoutTypePB::Document,
ViewDataFormatPB::DatabaseFormat => ViewLayoutTypePB::Grid,
};
let view = create_view(sdk, &self.app.id, &name, &desc, data_type, layout).await;
let view = create_view(sdk, &self.app.id, &name, &desc, layout).await;
self.view = view;
},
FolderScript::AssertView(view) => {
@ -372,7 +371,6 @@ pub async fn create_view(
app_id: &str,
name: &str,
desc: &str,
data_type: ViewDataFormatPB,
layout: ViewLayoutTypePB,
) -> ViewPB {
let request = CreateViewPayloadPB {
@ -380,9 +378,9 @@ pub async fn create_view(
name: name.to_string(),
desc: desc.to_string(),
thumbnail: None,
data_format: data_type,
layout,
initial_data: vec![],
ext: Default::default(),
};
FolderEventBuilder::new(sdk.clone())
.event(CreateView)