mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
Merge pull request #802 from AppFlowy-IO/fix/folder_serde_error
Try to fix deserialize folder reivsion error
This commit is contained in:
commit
58e8e0524e
@ -6,7 +6,7 @@ use flowy_revision::{
|
|||||||
};
|
};
|
||||||
use flowy_sync::util::make_delta_from_revisions;
|
use flowy_sync::util::make_delta_from_revisions;
|
||||||
use flowy_sync::{
|
use flowy_sync::{
|
||||||
client_folder::{FolderChange, FolderPad},
|
client_folder::{FolderChangeset, FolderPad},
|
||||||
entities::{revision::Revision, ws_data::ServerRevisionWSData},
|
entities::{revision::Revision, ws_data::ServerRevisionWSData},
|
||||||
};
|
};
|
||||||
use lib_infra::future::FutureResult;
|
use lib_infra::future::FutureResult;
|
||||||
@ -77,8 +77,8 @@ impl FolderEditor {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn apply_change(&self, change: FolderChange) -> FlowyResult<()> {
|
pub(crate) fn apply_change(&self, change: FolderChangeset) -> FlowyResult<()> {
|
||||||
let FolderChange { delta, md5 } = change;
|
let FolderChangeset { delta, md5 } = change;
|
||||||
let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
|
let (base_rev_id, rev_id) = self.rev_manager.next_rev_id_pair();
|
||||||
let delta_data = delta.json_bytes();
|
let delta_data = delta.json_bytes();
|
||||||
let revision = Revision::new(
|
let revision = Revision::new(
|
||||||
|
@ -17,7 +17,7 @@ log = "0.4.14"
|
|||||||
nanoid = "0.4.0"
|
nanoid = "0.4.0"
|
||||||
chrono = { version = "0.4" }
|
chrono = { version = "0.4" }
|
||||||
flowy-error-code = { path = "../flowy-error-code"}
|
flowy-error-code = { path = "../flowy-error-code"}
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive", "rc"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
serde_repr = "0.1"
|
serde_repr = "0.1"
|
||||||
|
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
use crate::revision::{TrashRevision, WorkspaceRevision};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
||||||
|
pub struct FolderRevision {
|
||||||
|
pub workspaces: Vec<Arc<WorkspaceRevision>>,
|
||||||
|
pub trash: Vec<Arc<TrashRevision>>,
|
||||||
|
}
|
@ -1,9 +1,11 @@
|
|||||||
mod app_rev;
|
mod app_rev;
|
||||||
|
mod folder_rev;
|
||||||
mod trash_rev;
|
mod trash_rev;
|
||||||
mod view_rev;
|
mod view_rev;
|
||||||
mod workspace_rev;
|
mod workspace_rev;
|
||||||
|
|
||||||
pub use app_rev::*;
|
pub use app_rev::*;
|
||||||
|
pub use folder_rev::*;
|
||||||
pub use trash_rev::*;
|
pub use trash_rev::*;
|
||||||
pub use view_rev::*;
|
pub use view_rev::*;
|
||||||
pub use workspace_rev::*;
|
pub use workspace_rev::*;
|
||||||
|
@ -33,7 +33,7 @@ impl<'de> serde::Deserialize<'de> for TrashTypeRevision {
|
|||||||
type Value = TrashTypeRevision;
|
type Value = TrashTypeRevision;
|
||||||
|
|
||||||
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
|
||||||
formatter.write_str("u8")
|
formatter.write_str("expected enum TrashTypeRevision with type: u8")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
|
fn visit_i8<E>(self, v: i8) -> Result<Self::Value, E>
|
||||||
|
@ -3,18 +3,17 @@ use crate::util::make_delta_from_revisions;
|
|||||||
use crate::{
|
use crate::{
|
||||||
client_folder::{default_folder_delta, FolderPad},
|
client_folder::{default_folder_delta, FolderPad},
|
||||||
entities::revision::Revision,
|
entities::revision::Revision,
|
||||||
errors::{CollaborateError, CollaborateResult},
|
errors::CollaborateResult,
|
||||||
};
|
};
|
||||||
|
|
||||||
use flowy_folder_data_model::revision::{TrashRevision, WorkspaceRevision};
|
use flowy_folder_data_model::revision::{TrashRevision, WorkspaceRevision};
|
||||||
use lib_ot::core::{PhantomAttributes, TextDelta, TextDeltaBuilder};
|
use lib_ot::core::PhantomAttributes;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) struct FolderPadBuilder {
|
pub(crate) struct FolderPadBuilder {
|
||||||
workspaces: Vec<Arc<WorkspaceRevision>>,
|
workspaces: Vec<WorkspaceRevision>,
|
||||||
trash: Vec<Arc<TrashRevision>>,
|
trash: Vec<TrashRevision>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderPadBuilder {
|
impl FolderPadBuilder {
|
||||||
@ -25,43 +24,28 @@ impl FolderPadBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn with_workspace(mut self, workspaces: Vec<WorkspaceRevision>) -> Self {
|
pub(crate) fn with_workspace(mut self, workspaces: Vec<WorkspaceRevision>) -> Self {
|
||||||
self.workspaces = workspaces.into_iter().map(Arc::new).collect();
|
self.workspaces = workspaces;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn with_trash(mut self, trash: Vec<TrashRevision>) -> Self {
|
pub(crate) fn with_trash(mut self, trash: Vec<TrashRevision>) -> Self {
|
||||||
self.trash = trash.into_iter().map(Arc::new).collect::<Vec<_>>();
|
self.trash = trash;
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn build_with_delta(self, mut delta: TextDelta) -> CollaborateResult<FolderPad> {
|
|
||||||
if delta.is_empty() {
|
|
||||||
delta = default_folder_delta();
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Reconvert from history if delta.to_str() failed.
|
|
||||||
let content = delta.content()?;
|
|
||||||
let mut folder: FolderPad = serde_json::from_str(&content).map_err(|e| {
|
|
||||||
tracing::error!("Deserialize folder from {} failed", content);
|
|
||||||
return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e));
|
|
||||||
})?;
|
|
||||||
folder.delta = delta;
|
|
||||||
Ok(folder)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn build_with_revisions(self, revisions: Vec<Revision>) -> CollaborateResult<FolderPad> {
|
pub(crate) fn build_with_revisions(self, revisions: Vec<Revision>) -> CollaborateResult<FolderPad> {
|
||||||
let folder_delta: FolderDelta = make_delta_from_revisions::<PhantomAttributes>(revisions)?;
|
let mut folder_delta: FolderDelta = make_delta_from_revisions::<PhantomAttributes>(revisions)?;
|
||||||
self.build_with_delta(folder_delta)
|
if folder_delta.is_empty() {
|
||||||
|
folder_delta = default_folder_delta();
|
||||||
|
}
|
||||||
|
FolderPad::from_delta(folder_delta)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(dead_code)]
|
||||||
pub(crate) fn build(self) -> CollaborateResult<FolderPad> {
|
pub(crate) fn build(self) -> CollaborateResult<FolderPad> {
|
||||||
let json = serde_json::to_string(&self)
|
FolderPad::new(self.workspaces, self.trash)
|
||||||
.map_err(|e| CollaborateError::internal().context(format!("Serialize to folder json str failed: {}", e)))?;
|
|
||||||
Ok(FolderPad {
|
|
||||||
workspaces: self.workspaces,
|
|
||||||
trash: self.trash,
|
|
||||||
delta: TextDeltaBuilder::new().insert(&json).build(),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,26 +8,33 @@ use crate::{
|
|||||||
},
|
},
|
||||||
errors::{CollaborateError, CollaborateResult},
|
errors::{CollaborateError, CollaborateResult},
|
||||||
};
|
};
|
||||||
use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
|
use flowy_folder_data_model::revision::{AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision};
|
||||||
use lib_infra::util::move_vec_element;
|
use lib_infra::util::move_vec_element;
|
||||||
use lib_ot::core::*;
|
use lib_ot::core::*;
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
#[derive(Debug, Deserialize, Serialize, Clone, Eq, PartialEq)]
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
pub struct FolderPad {
|
pub struct FolderPad {
|
||||||
pub(crate) workspaces: Vec<Arc<WorkspaceRevision>>,
|
folder_rev: FolderRevision,
|
||||||
pub(crate) trash: Vec<Arc<TrashRevision>>,
|
delta: FolderDelta,
|
||||||
#[serde(skip)]
|
|
||||||
pub(crate) delta: FolderDelta,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderPad {
|
impl FolderPad {
|
||||||
pub fn new(workspaces: Vec<WorkspaceRevision>, trash: Vec<TrashRevision>) -> CollaborateResult<Self> {
|
pub fn new(workspaces: Vec<WorkspaceRevision>, trash: Vec<TrashRevision>) -> CollaborateResult<Self> {
|
||||||
FolderPadBuilder::new()
|
let folder_rev = FolderRevision {
|
||||||
.with_workspace(workspaces)
|
workspaces: workspaces.into_iter().map(Arc::new).collect(),
|
||||||
.with_trash(trash)
|
trash: trash.into_iter().map(Arc::new).collect(),
|
||||||
.build()
|
};
|
||||||
|
Self::from_folder_rev(folder_rev)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn from_folder_rev(folder_rev: FolderRevision) -> CollaborateResult<Self> {
|
||||||
|
let json = serde_json::to_string(&folder_rev)
|
||||||
|
.map_err(|e| CollaborateError::internal().context(format!("Serialize to folder json str failed: {}", e)))?;
|
||||||
|
let delta = TextDeltaBuilder::new().insert(&json).build();
|
||||||
|
|
||||||
|
Ok(Self { folder_rev, delta })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
pub fn from_revisions(revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
||||||
@ -35,7 +42,14 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
|
pub fn from_delta(delta: FolderDelta) -> CollaborateResult<Self> {
|
||||||
FolderPadBuilder::new().build_with_delta(delta)
|
// TODO: Reconvert from history if delta.to_str() failed.
|
||||||
|
let content = delta.content()?;
|
||||||
|
let folder_rev: FolderRevision = serde_json::from_str(&content).map_err(|e| {
|
||||||
|
tracing::error!("Deserialize folder from {} failed", content);
|
||||||
|
return CollaborateError::internal().context(format!("Deserialize delta to folder failed: {}", e));
|
||||||
|
})?;
|
||||||
|
|
||||||
|
Ok(Self { folder_rev, delta })
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delta(&self) -> &FolderDelta {
|
pub fn delta(&self) -> &FolderDelta {
|
||||||
@ -44,8 +58,7 @@ impl FolderPad {
|
|||||||
|
|
||||||
pub fn reset_folder(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
|
pub fn reset_folder(&mut self, delta: FolderDelta) -> CollaborateResult<String> {
|
||||||
let folder = FolderPad::from_delta(delta)?;
|
let folder = FolderPad::from_delta(delta)?;
|
||||||
self.workspaces = folder.workspaces;
|
self.folder_rev = folder.folder_rev;
|
||||||
self.trash = folder.trash;
|
|
||||||
self.delta = folder.delta;
|
self.delta = folder.delta;
|
||||||
|
|
||||||
Ok(self.md5())
|
Ok(self.md5())
|
||||||
@ -57,13 +70,13 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_empty(&self) -> bool {
|
pub fn is_empty(&self) -> bool {
|
||||||
self.workspaces.is_empty() && self.trash.is_empty()
|
self.folder_rev.workspaces.is_empty() && self.folder_rev.trash.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)]
|
#[tracing::instrument(level = "trace", skip(self, workspace_rev), fields(workspace_name=%workspace_rev.name), err)]
|
||||||
pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> CollaborateResult<Option<FolderChange>> {
|
pub fn create_workspace(&mut self, workspace_rev: WorkspaceRevision) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let workspace = Arc::new(workspace_rev);
|
let workspace = Arc::new(workspace_rev);
|
||||||
if self.workspaces.contains(&workspace) {
|
if self.folder_rev.workspaces.contains(&workspace) {
|
||||||
tracing::warn!("[RootFolder]: Duplicate workspace");
|
tracing::warn!("[RootFolder]: Duplicate workspace");
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
@ -79,7 +92,7 @@ impl FolderPad {
|
|||||||
workspace_id: &str,
|
workspace_id: &str,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
desc: Option<String>,
|
desc: Option<String>,
|
||||||
) -> CollaborateResult<Option<FolderChange>> {
|
) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
self.with_workspace(workspace_id, |workspace| {
|
self.with_workspace(workspace_id, |workspace| {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
workspace.name = name;
|
workspace.name = name;
|
||||||
@ -96,6 +109,7 @@ impl FolderPad {
|
|||||||
match workspace_id {
|
match workspace_id {
|
||||||
None => {
|
None => {
|
||||||
let workspaces = self
|
let workspaces = self
|
||||||
|
.folder_rev
|
||||||
.workspaces
|
.workspaces
|
||||||
.iter()
|
.iter()
|
||||||
.map(|workspace| workspace.as_ref().clone())
|
.map(|workspace| workspace.as_ref().clone())
|
||||||
@ -103,7 +117,12 @@ impl FolderPad {
|
|||||||
Ok(workspaces)
|
Ok(workspaces)
|
||||||
}
|
}
|
||||||
Some(workspace_id) => {
|
Some(workspace_id) => {
|
||||||
if let Some(workspace) = self.workspaces.iter().find(|workspace| workspace.id == workspace_id) {
|
if let Some(workspace) = self
|
||||||
|
.folder_rev
|
||||||
|
.workspaces
|
||||||
|
.iter()
|
||||||
|
.find(|workspace| workspace.id == workspace_id)
|
||||||
|
{
|
||||||
Ok(vec![workspace.as_ref().clone()])
|
Ok(vec![workspace.as_ref().clone()])
|
||||||
} else {
|
} else {
|
||||||
Err(CollaborateError::record_not_found()
|
Err(CollaborateError::record_not_found()
|
||||||
@ -114,7 +133,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChange>> {
|
pub fn delete_workspace(&mut self, workspace_id: &str) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
self.modify_workspaces(|workspaces| {
|
self.modify_workspaces(|workspaces| {
|
||||||
workspaces.retain(|w| w.id != workspace_id);
|
workspaces.retain(|w| w.id != workspace_id);
|
||||||
Ok(Some(()))
|
Ok(Some(()))
|
||||||
@ -122,7 +141,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)]
|
#[tracing::instrument(level = "trace", skip(self), fields(app_name=%app_rev.name), err)]
|
||||||
pub fn create_app(&mut self, app_rev: AppRevision) -> CollaborateResult<Option<FolderChange>> {
|
pub fn create_app(&mut self, app_rev: AppRevision) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let workspace_id = app_rev.workspace_id.clone();
|
let workspace_id = app_rev.workspace_id.clone();
|
||||||
self.with_workspace(&workspace_id, move |workspace| {
|
self.with_workspace(&workspace_id, move |workspace| {
|
||||||
if workspace.apps.contains(&app_rev) {
|
if workspace.apps.contains(&app_rev) {
|
||||||
@ -135,7 +154,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_app(&self, app_id: &str) -> CollaborateResult<AppRevision> {
|
pub fn read_app(&self, app_id: &str) -> CollaborateResult<AppRevision> {
|
||||||
for workspace in &self.workspaces {
|
for workspace in &self.folder_rev.workspaces {
|
||||||
if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) {
|
if let Some(app) = workspace.apps.iter().find(|app| app.id == app_id) {
|
||||||
return Ok(app.clone());
|
return Ok(app.clone());
|
||||||
}
|
}
|
||||||
@ -148,7 +167,7 @@ impl FolderPad {
|
|||||||
app_id: &str,
|
app_id: &str,
|
||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
desc: Option<String>,
|
desc: Option<String>,
|
||||||
) -> CollaborateResult<Option<FolderChange>> {
|
) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
self.with_app(app_id, move |app| {
|
self.with_app(app_id, move |app| {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
app.name = name;
|
app.name = name;
|
||||||
@ -162,7 +181,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChange>> {
|
pub fn delete_app(&mut self, app_id: &str) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let app = self.read_app(app_id)?;
|
let app = self.read_app(app_id)?;
|
||||||
self.with_workspace(&app.workspace_id, |workspace| {
|
self.with_workspace(&app.workspace_id, |workspace| {
|
||||||
workspace.apps.retain(|app| app.id != app_id);
|
workspace.apps.retain(|app| app.id != app_id);
|
||||||
@ -171,7 +190,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub fn move_app(&mut self, app_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
pub fn move_app(&mut self, app_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let app = self.read_app(app_id)?;
|
let app = self.read_app(app_id)?;
|
||||||
self.with_workspace(&app.workspace_id, |workspace| {
|
self.with_workspace(&app.workspace_id, |workspace| {
|
||||||
match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to).map_err(internal_error)? {
|
match move_vec_element(&mut workspace.apps, |app| app.id == app_id, from, to).map_err(internal_error)? {
|
||||||
@ -182,7 +201,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)]
|
#[tracing::instrument(level = "trace", skip(self), fields(view_name=%view_rev.name), err)]
|
||||||
pub fn create_view(&mut self, view_rev: ViewRevision) -> CollaborateResult<Option<FolderChange>> {
|
pub fn create_view(&mut self, view_rev: ViewRevision) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let app_id = view_rev.belong_to_id.clone();
|
let app_id = view_rev.belong_to_id.clone();
|
||||||
self.with_app(&app_id, move |app| {
|
self.with_app(&app_id, move |app| {
|
||||||
if app.belongings.contains(&view_rev) {
|
if app.belongings.contains(&view_rev) {
|
||||||
@ -195,7 +214,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_view(&self, view_id: &str) -> CollaborateResult<ViewRevision> {
|
pub fn read_view(&self, view_id: &str) -> CollaborateResult<ViewRevision> {
|
||||||
for workspace in &self.workspaces {
|
for workspace in &self.folder_rev.workspaces {
|
||||||
for app in &(*workspace.apps) {
|
for app in &(*workspace.apps) {
|
||||||
if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) {
|
if let Some(view) = app.belongings.iter().find(|b| b.id == view_id) {
|
||||||
return Ok(view.clone());
|
return Ok(view.clone());
|
||||||
@ -206,7 +225,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<ViewRevision>> {
|
pub fn read_views(&self, belong_to_id: &str) -> CollaborateResult<Vec<ViewRevision>> {
|
||||||
for workspace in &self.workspaces {
|
for workspace in &self.folder_rev.workspaces {
|
||||||
for app in &(*workspace.apps) {
|
for app in &(*workspace.apps) {
|
||||||
if app.id == belong_to_id {
|
if app.id == belong_to_id {
|
||||||
return Ok(app.belongings.to_vec());
|
return Ok(app.belongings.to_vec());
|
||||||
@ -222,7 +241,7 @@ impl FolderPad {
|
|||||||
name: Option<String>,
|
name: Option<String>,
|
||||||
desc: Option<String>,
|
desc: Option<String>,
|
||||||
modified_time: i64,
|
modified_time: i64,
|
||||||
) -> CollaborateResult<Option<FolderChange>> {
|
) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let view = self.read_view(view_id)?;
|
let view = self.read_view(view_id)?;
|
||||||
self.with_view(&view.belong_to_id, view_id, |view| {
|
self.with_view(&view.belong_to_id, view_id, |view| {
|
||||||
if let Some(name) = name {
|
if let Some(name) = name {
|
||||||
@ -239,7 +258,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChange>> {
|
pub fn delete_view(&mut self, view_id: &str) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let view = self.read_view(view_id)?;
|
let view = self.read_view(view_id)?;
|
||||||
self.with_app(&view.belong_to_id, |app| {
|
self.with_app(&view.belong_to_id, |app| {
|
||||||
app.belongings.retain(|view| view.id != view_id);
|
app.belongings.retain(|view| view.id != view_id);
|
||||||
@ -248,7 +267,7 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[tracing::instrument(level = "trace", skip(self), err)]
|
#[tracing::instrument(level = "trace", skip(self), err)]
|
||||||
pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChange>> {
|
pub fn move_view(&mut self, view_id: &str, from: usize, to: usize) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
let view = self.read_view(view_id)?;
|
let view = self.read_view(view_id)?;
|
||||||
self.with_app(&view.belong_to_id, |app| {
|
self.with_app(&view.belong_to_id, |app| {
|
||||||
match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to).map_err(internal_error)? {
|
match move_vec_element(&mut app.belongings, |view| view.id == view_id, from, to).map_err(internal_error)? {
|
||||||
@ -258,7 +277,7 @@ impl FolderPad {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_trash(&mut self, trash: Vec<TrashRevision>) -> CollaborateResult<Option<FolderChange>> {
|
pub fn create_trash(&mut self, trash: Vec<TrashRevision>) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
self.with_trash(|t| {
|
self.with_trash(|t| {
|
||||||
let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<TrashRevision>>>();
|
let mut new_trash = trash.into_iter().map(Arc::new).collect::<Vec<Arc<TrashRevision>>>();
|
||||||
t.append(&mut new_trash);
|
t.append(&mut new_trash);
|
||||||
@ -270,18 +289,19 @@ impl FolderPad {
|
|||||||
pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<TrashRevision>> {
|
pub fn read_trash(&self, trash_id: Option<String>) -> CollaborateResult<Vec<TrashRevision>> {
|
||||||
match trash_id {
|
match trash_id {
|
||||||
None => Ok(self
|
None => Ok(self
|
||||||
|
.folder_rev
|
||||||
.trash
|
.trash
|
||||||
.iter()
|
.iter()
|
||||||
.map(|t| t.as_ref().clone())
|
.map(|t| t.as_ref().clone())
|
||||||
.collect::<Vec<TrashRevision>>()),
|
.collect::<Vec<TrashRevision>>()),
|
||||||
Some(trash_id) => match self.trash.iter().find(|t| t.id == trash_id) {
|
Some(trash_id) => match self.folder_rev.trash.iter().find(|t| t.id == trash_id) {
|
||||||
Some(trash) => Ok(vec![trash.as_ref().clone()]),
|
Some(trash) => Ok(vec![trash.as_ref().clone()]),
|
||||||
None => Ok(vec![]),
|
None => Ok(vec![]),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn delete_trash(&mut self, trash_ids: Option<Vec<String>>) -> CollaborateResult<Option<FolderChange>> {
|
pub fn delete_trash(&mut self, trash_ids: Option<Vec<String>>) -> CollaborateResult<Option<FolderChangeset>> {
|
||||||
match trash_ids {
|
match trash_ids {
|
||||||
None => self.with_trash(|trash| {
|
None => self.with_trash(|trash| {
|
||||||
trash.clear();
|
trash.clear();
|
||||||
@ -299,18 +319,18 @@ impl FolderPad {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn to_json(&self) -> CollaborateResult<String> {
|
pub fn to_json(&self) -> CollaborateResult<String> {
|
||||||
serde_json::to_string(self)
|
serde_json::to_string(&self.folder_rev)
|
||||||
.map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
|
.map_err(|e| CollaborateError::internal().context(format!("serial trash to json failed: {}", e)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FolderPad {
|
impl FolderPad {
|
||||||
fn modify_workspaces<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
|
fn modify_workspaces<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChangeset>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> CollaborateResult<Option<()>>,
|
F: FnOnce(&mut Vec<Arc<WorkspaceRevision>>) -> CollaborateResult<Option<()>>,
|
||||||
{
|
{
|
||||||
let cloned_self = self.clone();
|
let cloned_self = self.clone();
|
||||||
match f(&mut self.workspaces)? {
|
match f(&mut self.folder_rev.workspaces)? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let old = cloned_self.to_json()?;
|
let old = cloned_self.to_json()?;
|
||||||
@ -319,14 +339,14 @@ impl FolderPad {
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(delta) => {
|
Some(delta) => {
|
||||||
self.delta = self.delta.compose(&delta)?;
|
self.delta = self.delta.compose(&delta)?;
|
||||||
Ok(Some(FolderChange { delta, md5: self.md5() }))
|
Ok(Some(FolderChangeset { delta, md5: self.md5() }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
|
fn with_workspace<F>(&mut self, workspace_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut WorkspaceRevision) -> CollaborateResult<Option<()>>,
|
F: FnOnce(&mut WorkspaceRevision) -> CollaborateResult<Option<()>>,
|
||||||
{
|
{
|
||||||
@ -340,12 +360,12 @@ impl FolderPad {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_trash<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChange>>
|
fn with_trash<F>(&mut self, f: F) -> CollaborateResult<Option<FolderChangeset>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> CollaborateResult<Option<()>>,
|
F: FnOnce(&mut Vec<Arc<TrashRevision>>) -> CollaborateResult<Option<()>>,
|
||||||
{
|
{
|
||||||
let cloned_self = self.clone();
|
let cloned_self = self.clone();
|
||||||
match f(&mut self.trash)? {
|
match f(&mut self.folder_rev.trash)? {
|
||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
let old = cloned_self.to_json()?;
|
let old = cloned_self.to_json()?;
|
||||||
@ -354,18 +374,19 @@ impl FolderPad {
|
|||||||
None => Ok(None),
|
None => Ok(None),
|
||||||
Some(delta) => {
|
Some(delta) => {
|
||||||
self.delta = self.delta.compose(&delta)?;
|
self.delta = self.delta.compose(&delta)?;
|
||||||
Ok(Some(FolderChange { delta, md5: self.md5() }))
|
Ok(Some(FolderChangeset { delta, md5: self.md5() }))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_app<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
|
fn with_app<F>(&mut self, app_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut AppRevision) -> CollaborateResult<Option<()>>,
|
F: FnOnce(&mut AppRevision) -> CollaborateResult<Option<()>>,
|
||||||
{
|
{
|
||||||
let workspace_id = match self
|
let workspace_id = match self
|
||||||
|
.folder_rev
|
||||||
.workspaces
|
.workspaces
|
||||||
.iter()
|
.iter()
|
||||||
.find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
|
.find(|workspace| workspace.apps.iter().any(|app| app.id == app_id))
|
||||||
@ -383,7 +404,7 @@ impl FolderPad {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<FolderChange>>
|
fn with_view<F>(&mut self, belong_to_id: &str, view_id: &str, f: F) -> CollaborateResult<Option<FolderChangeset>>
|
||||||
where
|
where
|
||||||
F: FnOnce(&mut ViewRevision) -> CollaborateResult<Option<()>>,
|
F: FnOnce(&mut ViewRevision) -> CollaborateResult<Option<()>>,
|
||||||
{
|
{
|
||||||
@ -414,14 +435,13 @@ pub fn initial_folder_delta(folder_pad: &FolderPad) -> CollaborateResult<FolderD
|
|||||||
impl std::default::Default for FolderPad {
|
impl std::default::Default for FolderPad {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
FolderPad {
|
FolderPad {
|
||||||
workspaces: vec![],
|
folder_rev: FolderRevision::default(),
|
||||||
trash: vec![],
|
|
||||||
delta: default_folder_delta(),
|
delta: default_folder_delta(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FolderChange {
|
pub struct FolderChangeset {
|
||||||
pub delta: FolderDelta,
|
pub delta: FolderDelta,
|
||||||
/// md5: the md5 of the FolderPad's delta after applying the change.
|
/// md5: the md5 of the FolderPad's delta after applying the change.
|
||||||
pub md5: String,
|
pub md5: String,
|
||||||
@ -433,8 +453,11 @@ mod tests {
|
|||||||
use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
|
use crate::{client_folder::folder_pad::FolderPad, entities::folder::FolderDelta};
|
||||||
use chrono::Utc;
|
use chrono::Utc;
|
||||||
|
|
||||||
use flowy_folder_data_model::revision::{AppRevision, TrashRevision, ViewRevision, WorkspaceRevision};
|
use flowy_folder_data_model::revision::{
|
||||||
|
AppRevision, FolderRevision, TrashRevision, ViewRevision, WorkspaceRevision,
|
||||||
|
};
|
||||||
use lib_ot::core::{OperationTransform, TextDelta, TextDeltaBuilder};
|
use lib_ot::core::{OperationTransform, TextDelta, TextDeltaBuilder};
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn folder_add_workspace() {
|
fn folder_add_workspace() {
|
||||||
@ -747,14 +770,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_folder() -> (FolderPad, FolderDelta, WorkspaceRevision) {
|
fn test_folder() -> (FolderPad, FolderDelta, WorkspaceRevision) {
|
||||||
let mut folder = FolderPad::default();
|
let folder_rev = FolderRevision::default();
|
||||||
let folder_json = serde_json::to_string(&folder).unwrap();
|
let folder_json = serde_json::to_string(&folder_rev).unwrap();
|
||||||
let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
|
let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
|
||||||
|
|
||||||
let mut workspace_rev = WorkspaceRevision::default();
|
let mut workspace_rev = WorkspaceRevision::default();
|
||||||
workspace_rev.name = "😁 my first workspace".to_owned();
|
workspace_rev.name = "😁 my first workspace".to_owned();
|
||||||
workspace_rev.id = "1".to_owned();
|
workspace_rev.id = "1".to_owned();
|
||||||
|
|
||||||
|
let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
|
||||||
|
|
||||||
delta = delta
|
delta = delta
|
||||||
.compose(&folder.create_workspace(workspace_rev.clone()).unwrap().unwrap().delta)
|
.compose(&folder.create_workspace(workspace_rev.clone()).unwrap().unwrap().delta)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
@ -763,16 +788,16 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_app_folder() -> (FolderPad, FolderDelta, AppRevision) {
|
fn test_app_folder() -> (FolderPad, FolderDelta, AppRevision) {
|
||||||
let (mut folder, mut initial_delta, workspace) = test_folder();
|
let (mut folder_rev, mut initial_delta, workspace) = test_folder();
|
||||||
let mut app_rev = AppRevision::default();
|
let mut app_rev = AppRevision::default();
|
||||||
app_rev.workspace_id = workspace.id;
|
app_rev.workspace_id = workspace.id;
|
||||||
app_rev.name = "😁 my first app".to_owned();
|
app_rev.name = "😁 my first app".to_owned();
|
||||||
|
|
||||||
initial_delta = initial_delta
|
initial_delta = initial_delta
|
||||||
.compose(&folder.create_app(app_rev.clone()).unwrap().unwrap().delta)
|
.compose(&folder_rev.create_app(app_rev.clone()).unwrap().unwrap().delta)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
(folder, initial_delta, app_rev)
|
(folder_rev, initial_delta, app_rev)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_view_folder() -> (FolderPad, FolderDelta, ViewRevision) {
|
fn test_view_folder() -> (FolderPad, FolderDelta, ViewRevision) {
|
||||||
@ -789,14 +814,14 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn test_trash() -> (FolderPad, FolderDelta, TrashRevision) {
|
fn test_trash() -> (FolderPad, FolderDelta, TrashRevision) {
|
||||||
let mut folder = FolderPad::default();
|
let folder_rev = FolderRevision::default();
|
||||||
let folder_json = serde_json::to_string(&folder).unwrap();
|
let folder_json = serde_json::to_string(&folder_rev).unwrap();
|
||||||
let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
|
let mut delta = TextDeltaBuilder::new().insert(&folder_json).build();
|
||||||
|
|
||||||
let mut trash_rev = TrashRevision::default();
|
let mut trash_rev = TrashRevision::default();
|
||||||
trash_rev.name = "🚽 my first trash".to_owned();
|
trash_rev.name = "🚽 my first trash".to_owned();
|
||||||
trash_rev.id = "1".to_owned();
|
trash_rev.id = "1".to_owned();
|
||||||
|
let mut folder = FolderPad::from_folder_rev(folder_rev).unwrap();
|
||||||
delta = delta
|
delta = delta
|
||||||
.compose(
|
.compose(
|
||||||
&folder
|
&folder
|
||||||
@ -823,8 +848,11 @@ mod tests {
|
|||||||
let json1 = old.to_json().unwrap();
|
let json1 = old.to_json().unwrap();
|
||||||
let json2 = new.to_json().unwrap();
|
let json2 = new.to_json().unwrap();
|
||||||
|
|
||||||
let expect_folder: FolderPad = serde_json::from_str(expected).unwrap();
|
// format the json str
|
||||||
assert_eq!(json1, expect_folder.to_json().unwrap());
|
let folder_rev: FolderRevision = serde_json::from_str(expected).unwrap();
|
||||||
|
let expected = serde_json::to_string(&folder_rev).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(json1, expected);
|
||||||
assert_eq!(json1, json2);
|
assert_eq!(json1, json2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,432 +0,0 @@
|
|||||||
use crate::entities::revision::{md5, RepeatedRevision, Revision};
|
|
||||||
use crate::errors::{internal_error, CollaborateError, CollaborateResult};
|
|
||||||
use crate::util::{cal_diff, make_delta_from_revisions};
|
|
||||||
use bytes::Bytes;
|
|
||||||
use flowy_grid_data_model::entities::{
|
|
||||||
gen_block_id, gen_grid_id, FieldChangesetParams, FieldMeta, FieldOrder, FieldType, GridBlockInfoChangeset,
|
|
||||||
GridBlockMetaSnapshot, GridMeta,
|
|
||||||
};
|
|
||||||
use lib_infra::util::move_vec_element;
|
|
||||||
use lib_ot::core::{OperationTransformable, PlainTextAttributes, PlainTextDelta, PlainTextDeltaBuilder};
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
pub type GridMetaDelta = PlainTextDelta;
|
|
||||||
pub type GridDeltaBuilder = PlainTextDeltaBuilder;
|
|
||||||
|
|
||||||
pub struct GridMetaPad {
|
|
||||||
pub(crate) grid_meta: Arc<GridMeta>,
|
|
||||||
pub(crate) delta: GridMetaDelta,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait JsonDeserializer {
|
|
||||||
fn deserialize(&self, type_option_data: Vec<u8>) -> CollaborateResult<String>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl GridMetaPad {
|
|
||||||
pub async fn duplicate_grid_meta(&self) -> (Vec<FieldMeta>, Vec<GridBlockMetaSnapshot>) {
|
|
||||||
let fields = self.grid_meta.fields.to_vec();
|
|
||||||
|
|
||||||
let blocks = self
|
|
||||||
.grid_meta
|
|
||||||
.blocks
|
|
||||||
.iter()
|
|
||||||
.map(|block| {
|
|
||||||
let mut duplicated_block = block.clone();
|
|
||||||
duplicated_block.block_id = gen_block_id();
|
|
||||||
duplicated_block
|
|
||||||
})
|
|
||||||
.collect::<Vec<GridBlockMetaSnapshot>>();
|
|
||||||
|
|
||||||
(fields, blocks)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_delta(delta: GridMetaDelta) -> CollaborateResult<Self> {
|
|
||||||
let s = delta.to_str()?;
|
|
||||||
let grid: GridMeta = serde_json::from_str(&s)
|
|
||||||
.map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to grid failed: {}", e)))?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
grid_meta: Arc::new(grid),
|
|
||||||
delta,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn from_revisions(_grid_id: &str, revisions: Vec<Revision>) -> CollaborateResult<Self> {
|
|
||||||
let grid_delta: GridMetaDelta = make_delta_from_revisions::<PlainTextAttributes>(revisions)?;
|
|
||||||
Self::from_delta(grid_delta)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tracing::instrument(level = "debug", skip_all, err)]
|
|
||||||
pub fn create_field_meta(
|
|
||||||
&mut self,
|
|
||||||
new_field_meta: FieldMeta,
|
|
||||||
start_field_id: Option<String>,
|
|
||||||
) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(|grid_meta| {
|
|
||||||
// Check if the field exists or not
|
|
||||||
if grid_meta
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.any(|field_meta| field_meta.id == new_field_meta.id)
|
|
||||||
{
|
|
||||||
tracing::error!("Duplicate grid field");
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
|
|
||||||
let insert_index = match start_field_id {
|
|
||||||
None => None,
|
|
||||||
Some(start_field_id) => grid_meta.fields.iter().position(|field| field.id == start_field_id),
|
|
||||||
};
|
|
||||||
|
|
||||||
match insert_index {
|
|
||||||
None => grid_meta.fields.push(new_field_meta),
|
|
||||||
Some(index) => grid_meta.fields.insert(index, new_field_meta),
|
|
||||||
}
|
|
||||||
Ok(Some(()))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delete_field_meta(&mut self, field_id: &str) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(
|
|
||||||
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(index) => {
|
|
||||||
grid_meta.fields.remove(index);
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn duplicate_field_meta(
|
|
||||||
&mut self,
|
|
||||||
field_id: &str,
|
|
||||||
duplicated_field_id: &str,
|
|
||||||
) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(
|
|
||||||
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(index) => {
|
|
||||||
let mut duplicate_field_meta = grid_meta.fields[index].clone();
|
|
||||||
duplicate_field_meta.id = duplicated_field_id.to_string();
|
|
||||||
duplicate_field_meta.name = format!("{} (copy)", duplicate_field_meta.name);
|
|
||||||
grid_meta.fields.insert(index + 1, duplicate_field_meta);
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn switch_to_field<B>(
|
|
||||||
&mut self,
|
|
||||||
field_id: &str,
|
|
||||||
field_type: FieldType,
|
|
||||||
type_option_json_builder: B,
|
|
||||||
) -> CollaborateResult<Option<GridChangeset>>
|
|
||||||
where
|
|
||||||
B: FnOnce(&FieldType) -> String,
|
|
||||||
{
|
|
||||||
self.modify_grid(|grid_meta| {
|
|
||||||
//
|
|
||||||
match grid_meta.fields.iter_mut().find(|field_meta| field_meta.id == field_id) {
|
|
||||||
None => {
|
|
||||||
tracing::warn!("Can not find the field with id: {}", field_id);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Some(field_meta) => {
|
|
||||||
if field_meta.get_type_option_str(&field_type).is_none() {
|
|
||||||
let type_option_json = type_option_json_builder(&field_type);
|
|
||||||
field_meta.insert_type_option_str(&field_type, type_option_json);
|
|
||||||
}
|
|
||||||
|
|
||||||
field_meta.field_type = field_type;
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_field_meta<T: JsonDeserializer>(
|
|
||||||
&mut self,
|
|
||||||
changeset: FieldChangesetParams,
|
|
||||||
deserializer: T,
|
|
||||||
) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
let field_id = changeset.field_id.clone();
|
|
||||||
self.modify_field(&field_id, |field| {
|
|
||||||
let mut is_changed = None;
|
|
||||||
if let Some(name) = changeset.name {
|
|
||||||
field.name = name;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(desc) = changeset.desc {
|
|
||||||
field.desc = desc;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(field_type) = changeset.field_type {
|
|
||||||
field.field_type = field_type;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(frozen) = changeset.frozen {
|
|
||||||
field.frozen = frozen;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(visibility) = changeset.visibility {
|
|
||||||
field.visibility = visibility;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(width) = changeset.width {
|
|
||||||
field.width = width;
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(type_option_data) = changeset.type_option_data {
|
|
||||||
match deserializer.deserialize(type_option_data) {
|
|
||||||
Ok(json_str) => {
|
|
||||||
let field_type = field.field_type.clone();
|
|
||||||
field.insert_type_option_str(&field_type, json_str);
|
|
||||||
is_changed = Some(())
|
|
||||||
}
|
|
||||||
Err(err) => {
|
|
||||||
tracing::error!("Deserialize data to type option json failed: {}", err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(is_changed)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_field_meta(&self, field_id: &str) -> Option<(usize, &FieldMeta)> {
|
|
||||||
self.grid_meta
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.enumerate()
|
|
||||||
.find(|(_, field)| field.id == field_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_field_meta(&mut self, field_meta: FieldMeta) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(
|
|
||||||
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_meta.id) {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(index) => {
|
|
||||||
grid_meta.fields.remove(index);
|
|
||||||
grid_meta.fields.insert(index, field_meta);
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn move_field(
|
|
||||||
&mut self,
|
|
||||||
field_id: &str,
|
|
||||||
from_index: usize,
|
|
||||||
to_index: usize,
|
|
||||||
) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(|grid_meta| {
|
|
||||||
match move_vec_element(
|
|
||||||
&mut grid_meta.fields,
|
|
||||||
|field| field.id == field_id,
|
|
||||||
from_index,
|
|
||||||
to_index,
|
|
||||||
)
|
|
||||||
.map_err(internal_error)?
|
|
||||||
{
|
|
||||||
true => Ok(Some(())),
|
|
||||||
false => Ok(None),
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contain_field(&self, field_id: &str) -> bool {
|
|
||||||
self.grid_meta.fields.iter().any(|field| field.id == field_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_field_orders(&self) -> Vec<FieldOrder> {
|
|
||||||
self.grid_meta.fields.iter().map(FieldOrder::from).collect()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_field_metas(&self, field_orders: Option<Vec<FieldOrder>>) -> CollaborateResult<Vec<FieldMeta>> {
|
|
||||||
match field_orders {
|
|
||||||
None => Ok(self.grid_meta.fields.clone()),
|
|
||||||
Some(field_orders) => {
|
|
||||||
let field_by_field_id = self
|
|
||||||
.grid_meta
|
|
||||||
.fields
|
|
||||||
.iter()
|
|
||||||
.map(|field| (&field.id, field))
|
|
||||||
.collect::<HashMap<&String, &FieldMeta>>();
|
|
||||||
|
|
||||||
let fields = field_orders
|
|
||||||
.iter()
|
|
||||||
.flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
|
|
||||||
None => {
|
|
||||||
tracing::error!("Can't find the field with id: {}", field_order.field_id);
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Some(field) => Some((*field).clone()),
|
|
||||||
})
|
|
||||||
.collect::<Vec<FieldMeta>>();
|
|
||||||
Ok(fields)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_block_meta(&mut self, block: GridBlockMetaSnapshot) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
self.modify_grid(|grid_meta| {
|
|
||||||
if grid_meta.blocks.iter().any(|b| b.block_id == block.block_id) {
|
|
||||||
tracing::warn!("Duplicate grid block");
|
|
||||||
Ok(None)
|
|
||||||
} else {
|
|
||||||
match grid_meta.blocks.last() {
|
|
||||||
None => grid_meta.blocks.push(block),
|
|
||||||
Some(last_block) => {
|
|
||||||
if last_block.start_row_index > block.start_row_index
|
|
||||||
&& last_block.len() > block.start_row_index
|
|
||||||
{
|
|
||||||
let msg = "GridBlock's start_row_index should be greater than the last_block's start_row_index and its len".to_string();
|
|
||||||
return Err(CollaborateError::internal().context(msg))
|
|
||||||
}
|
|
||||||
grid_meta.blocks.push(block);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Some(()))
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_block_metas(&self) -> Vec<GridBlockMetaSnapshot> {
|
|
||||||
self.grid_meta.blocks.clone()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_block_meta(&mut self, changeset: GridBlockInfoChangeset) -> CollaborateResult<Option<GridChangeset>> {
|
|
||||||
let block_id = changeset.block_id.clone();
|
|
||||||
self.modify_block(&block_id, |block| {
|
|
||||||
let mut is_changed = None;
|
|
||||||
|
|
||||||
if let Some(row_count) = changeset.row_count {
|
|
||||||
block.row_count = row_count;
|
|
||||||
is_changed = Some(());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(start_row_index) = changeset.start_row_index {
|
|
||||||
block.start_row_index = start_row_index;
|
|
||||||
is_changed = Some(());
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(is_changed)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn md5(&self) -> String {
|
|
||||||
md5(&self.delta.to_delta_bytes())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delta_str(&self) -> String {
|
|
||||||
self.delta.to_delta_str()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn delta_bytes(&self) -> Bytes {
|
|
||||||
self.delta.to_delta_bytes()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn fields(&self) -> &[FieldMeta] {
|
|
||||||
&self.grid_meta.fields
|
|
||||||
}
|
|
||||||
|
|
||||||
fn modify_grid<F>(&mut self, f: F) -> CollaborateResult<Option<GridChangeset>>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut GridMeta) -> CollaborateResult<Option<()>>,
|
|
||||||
{
|
|
||||||
let cloned_grid = self.grid_meta.clone();
|
|
||||||
match f(Arc::make_mut(&mut self.grid_meta))? {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(_) => {
|
|
||||||
let old = json_from_grid(&cloned_grid)?;
|
|
||||||
let new = json_from_grid(&self.grid_meta)?;
|
|
||||||
match cal_diff::<PlainTextAttributes>(old, new) {
|
|
||||||
None => Ok(None),
|
|
||||||
Some(delta) => {
|
|
||||||
self.delta = self.delta.compose(&delta)?;
|
|
||||||
Ok(Some(GridChangeset { delta, md5: self.md5() }))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify_block<F>(&mut self, block_id: &str, f: F) -> CollaborateResult<Option<GridChangeset>>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut GridBlockMetaSnapshot) -> CollaborateResult<Option<()>>,
|
|
||||||
{
|
|
||||||
self.modify_grid(
|
|
||||||
|grid_meta| match grid_meta.blocks.iter().position(|block| block.block_id == block_id) {
|
|
||||||
None => {
|
|
||||||
tracing::warn!("[GridMetaPad]: Can't find any block with id: {}", block_id);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Some(index) => f(&mut grid_meta.blocks[index]),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn modify_field<F>(&mut self, field_id: &str, f: F) -> CollaborateResult<Option<GridChangeset>>
|
|
||||||
where
|
|
||||||
F: FnOnce(&mut FieldMeta) -> CollaborateResult<Option<()>>,
|
|
||||||
{
|
|
||||||
self.modify_grid(
|
|
||||||
|grid_meta| match grid_meta.fields.iter().position(|field| field.id == field_id) {
|
|
||||||
None => {
|
|
||||||
tracing::warn!("[GridMetaPad]: Can't find any field with id: {}", field_id);
|
|
||||||
Ok(None)
|
|
||||||
}
|
|
||||||
Some(index) => f(&mut grid_meta.fields[index]),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn json_from_grid(grid: &Arc<GridMeta>) -> CollaborateResult<String> {
|
|
||||||
let json = serde_json::to_string(grid)
|
|
||||||
.map_err(|err| internal_error(format!("Serialize grid to json str failed. {:?}", err)))?;
|
|
||||||
Ok(json)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct GridChangeset {
|
|
||||||
pub delta: GridMetaDelta,
|
|
||||||
/// md5: the md5 of the grid after applying the change.
|
|
||||||
pub md5: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_grid_delta(grid_meta: &GridMeta) -> GridMetaDelta {
|
|
||||||
let json = serde_json::to_string(&grid_meta).unwrap();
|
|
||||||
PlainTextDeltaBuilder::new().insert(&json).build()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn make_grid_revisions(user_id: &str, grid_meta: &GridMeta) -> RepeatedRevision {
|
|
||||||
let delta = make_grid_delta(grid_meta);
|
|
||||||
let bytes = delta.to_delta_bytes();
|
|
||||||
let revision = Revision::initial_revision(user_id, &grid_meta.grid_id, bytes);
|
|
||||||
revision.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
impl std::default::Default for GridMetaPad {
|
|
||||||
fn default() -> Self {
|
|
||||||
let grid = GridMeta {
|
|
||||||
grid_id: gen_grid_id(),
|
|
||||||
fields: vec![],
|
|
||||||
blocks: vec![],
|
|
||||||
};
|
|
||||||
let delta = make_grid_delta(&grid);
|
|
||||||
GridMetaPad {
|
|
||||||
grid_meta: Arc::new(grid),
|
|
||||||
delta,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -53,8 +53,11 @@ impl GridRevisionPad {
|
|||||||
|
|
||||||
pub fn from_delta(delta: GridRevisionDelta) -> CollaborateResult<Self> {
|
pub fn from_delta(delta: GridRevisionDelta) -> CollaborateResult<Self> {
|
||||||
let content = delta.content()?;
|
let content = delta.content()?;
|
||||||
let grid: GridRevision = serde_json::from_str(&content)
|
let grid: GridRevision = serde_json::from_str(&content).map_err(|e| {
|
||||||
.map_err(|e| CollaborateError::internal().context(format!("Deserialize delta to grid failed: {}", e)))?;
|
let msg = format!("Deserialize delta to grid failed: {}", e);
|
||||||
|
tracing::error!("{}", msg);
|
||||||
|
CollaborateError::internal().context(msg)
|
||||||
|
})?;
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
grid_rev: Arc::new(grid),
|
grid_rev: Arc::new(grid),
|
||||||
|
@ -4,6 +4,7 @@ use crate::rich_text::RichTextAttributes;
|
|||||||
pub type RichTextOpBuilder = OperationsBuilder<RichTextAttributes>;
|
pub type RichTextOpBuilder = OperationsBuilder<RichTextAttributes>;
|
||||||
pub type PlainTextOpBuilder = OperationsBuilder<PhantomAttributes>;
|
pub type PlainTextOpBuilder = OperationsBuilder<PhantomAttributes>;
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
pub struct OperationsBuilder<T: Attributes> {
|
pub struct OperationsBuilder<T: Attributes> {
|
||||||
operations: Vec<Operation<T>>,
|
operations: Vec<Operation<T>>,
|
||||||
}
|
}
|
||||||
@ -13,17 +14,17 @@ where
|
|||||||
T: Attributes,
|
T: Attributes,
|
||||||
{
|
{
|
||||||
pub fn new() -> OperationsBuilder<T> {
|
pub fn new() -> OperationsBuilder<T> {
|
||||||
OperationsBuilder { operations: vec![] }
|
OperationsBuilder::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain_with_attributes(mut self, n: usize, attributes: T) -> OperationsBuilder<T> {
|
pub fn retain_with_attributes(mut self, n: usize, attributes: T) -> OperationsBuilder<T> {
|
||||||
let retain = Operation::retain_with_attributes(n.into(), attributes);
|
let retain = Operation::retain_with_attributes(n, attributes);
|
||||||
self.operations.push(retain);
|
self.operations.push(retain);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn retain(mut self, n: usize) -> OperationsBuilder<T> {
|
pub fn retain(mut self, n: usize) -> OperationsBuilder<T> {
|
||||||
let retain = Operation::retain(n.into());
|
let retain = Operation::retain(n);
|
||||||
self.operations.push(retain);
|
self.operations.push(retain);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
@ -34,13 +35,13 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert_with_attributes(mut self, s: &str, attributes: T) -> OperationsBuilder<T> {
|
pub fn insert_with_attributes(mut self, s: &str, attributes: T) -> OperationsBuilder<T> {
|
||||||
let insert = Operation::insert_with_attributes(s.into(), attributes);
|
let insert = Operation::insert_with_attributes(s, attributes);
|
||||||
self.operations.push(insert);
|
self.operations.push(insert);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(mut self, s: &str) -> OperationsBuilder<T> {
|
pub fn insert(mut self, s: &str) -> OperationsBuilder<T> {
|
||||||
let insert = Operation::insert(s.into());
|
let insert = Operation::insert(s);
|
||||||
self.operations.push(insert);
|
self.operations.push(insert);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user