This commit is contained in:
appflowy 2021-12-29 00:34:00 +08:00
parent e4e40ebe20
commit 152cb17701
42 changed files with 528 additions and 363 deletions

View File

@ -7,10 +7,11 @@ use crate::{
util::sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use backend_service::errors::{invalid_params, ServerError};
use bytes::Bytes;
use chrono::Utc;
use flowy_collaboration::{
entities::revision::{RevType, Revision},
protobuf::{CreateDocParams, RepeatedRevision},
entities::revision::{RepeatedRevision, RevType, Revision},
protobuf::CreateDocParams,
};
use flowy_core_data_model::{
parser::{
@ -71,12 +72,13 @@ pub(crate) async fn create_view(
params: CreateViewParams,
user_id: &str,
) -> Result<View, ServerError> {
let view_id = check_view_id(params.view_id.clone())?;
let name = ViewName::parse(params.name).map_err(invalid_params)?;
let belong_to_id = AppId::parse(params.belong_to_id).map_err(invalid_params)?;
let thumbnail = ViewThumbnail::parse(params.thumbnail).map_err(invalid_params)?;
let desc = ViewDesc::parse(params.desc).map_err(invalid_params)?;
let (sql, args, view) = NewViewSqlBuilder::new(belong_to_id.as_ref())
let (sql, args, view) = NewViewSqlBuilder::new(view_id, belong_to_id.as_ref())
.name(name.as_ref())
.desc(desc.as_ref())
.thumbnail(thumbnail.as_ref())
@ -88,18 +90,13 @@ pub(crate) async fn create_view(
.await
.map_err(map_sqlx_error)?;
let doc_id = view.id.clone();
let revision: flowy_collaboration::protobuf::Revision =
Revision::initial_revision(user_id, &doc_id, RevType::Remote)
.try_into()
.unwrap();
let mut repeated_revision = RepeatedRevision::new();
repeated_revision.set_items(vec![revision].into());
let delta_data = Bytes::from(params.view_data);
let md5 = format!("{:x}", md5::compute(&delta_data));
let revision = Revision::new(&view.id, 0, 0, delta_data, RevType::Remote, user_id, md5);
let repeated_revision = RepeatedRevision::new(vec![revision]);
let mut create_doc_params = CreateDocParams::new();
create_doc_params.set_revisions(repeated_revision);
create_doc_params.set_revisions(repeated_revision.try_into().unwrap());
create_doc_params.set_id(view.id.clone());
let _ = create_document(&kv_store, create_doc_params).await?;
Ok(view)

View File

@ -16,12 +16,11 @@ pub struct NewViewSqlBuilder {
}
impl NewViewSqlBuilder {
pub fn new(belong_to_id: &str) -> Self {
let uuid = uuid::Uuid::new_v4();
pub fn new(view_id: Uuid, belong_to_id: &str) -> Self {
let time = Utc::now();
let table = ViewTable {
id: uuid,
id: view_id,
belong_to_id: belong_to_id.to_string(),
name: "".to_string(),
description: "".to_string(),
@ -94,13 +93,17 @@ impl NewViewSqlBuilder {
pub(crate) fn check_view_ids(ids: Vec<String>) -> Result<Vec<Uuid>, ServerError> {
let mut view_ids = vec![];
for id in ids {
let view_id = ViewId::parse(id).map_err(invalid_params)?;
let view_id = Uuid::parse_str(view_id.as_ref())?;
view_ids.push(view_id);
view_ids.push(check_view_id(id)?);
}
Ok(view_ids)
}
pub(crate) fn check_view_id(id: String) -> Result<Uuid, ServerError> {
let view_id = ViewId::parse(id).map_err(invalid_params)?;
let view_id = Uuid::parse_str(view_id.as_ref())?;
Ok(view_id)
}
#[derive(Debug, Clone, sqlx::FromRow)]
pub struct ViewTable {
pub(crate) id: uuid::Uuid,

View File

@ -1,7 +1,13 @@
use crate::{
context::FlowyPersistence,
entities::logged_user::LoggedUser,
services::core::view::{create_view, delete_view, persistence::check_view_ids, read_view, update_view},
services::core::view::{
create_view,
delete_view,
persistence::{check_view_id, check_view_ids},
read_view,
update_view,
},
util::serde_ext::parse_from_payload,
};
use actix_web::{
@ -62,7 +68,7 @@ pub async fn read_handler(payload: Payload, pool: Data<PgPool>, user: LoggedUser
pub async fn update_handler(payload: Payload, pool: Data<PgPool>) -> Result<HttpResponse, ServerError> {
let params: UpdateViewParams = parse_from_payload(payload).await?;
let view_id = check_view_ids(vec![params.view_id.clone()])?.pop().unwrap();
let view_id = check_view_id(params.view_id.clone())?;
let name = match params.has_name() {
false => None,
true => Some(ViewName::parse(params.get_name().to_owned()).map_err(invalid_params)?.0),

View File

@ -1,6 +1,6 @@
#![allow(clippy::module_inception)]
pub(crate) mod persistence;
pub mod persistence;
pub(crate) mod router;
pub(crate) mod ws_actor;
pub(crate) mod ws_receiver;

View File

@ -1,6 +1,5 @@
use crate::{
context::FlowyPersistence,
services::kv::{KVStore, KVTransaction, KeyValue},
services::kv::{KVStore, KeyValue},
util::serde_ext::parse_from_bytes,
};
use anyhow::Context;
@ -16,7 +15,7 @@ use flowy_collaboration::protobuf::{
};
use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
use protobuf::Message;
use sqlx::PgPool;
use std::sync::Arc;
use uuid::Uuid;
@ -30,14 +29,12 @@ pub(crate) async fn create_document(
Ok(())
}
#[tracing::instrument(level = "debug", skip(persistence), err)]
pub(crate) async fn read_document(
persistence: &Arc<FlowyPersistence>,
#[tracing::instrument(level = "debug", skip(kv_store), err)]
pub async fn read_document(
kv_store: &Arc<DocumentKVPersistence>,
params: DocIdentifier,
) -> Result<DocumentInfo, ServerError> {
let _ = Uuid::parse_str(&params.doc_id).context("Parse document id to uuid failed")?;
let kv_store = persistence.kv_store();
let revisions = kv_store.batch_get_revisions(&params.doc_id, None).await?;
make_doc_from_revisions(&params.doc_id, revisions)
}

View File

@ -9,7 +9,7 @@ use actix_web::{
};
use backend_service::{errors::ServerError, response::FlowyResponse};
use flowy_collaboration::protobuf::{CreateDocParams, DocIdentifier, ResetDocumentParams};
use sqlx::PgPool;
use std::sync::Arc;
pub async fn create_document_handler(
@ -28,7 +28,8 @@ pub async fn read_document_handler(
persistence: Data<Arc<FlowyPersistence>>,
) -> Result<HttpResponse, ServerError> {
let params: DocIdentifier = parse_from_payload(payload).await?;
let doc = read_document(persistence.get_ref(), params).await?;
let kv_store = persistence.kv_store();
let doc = read_document(&kv_store, params).await?;
let response = FlowyResponse::success().pb(doc)?;
Ok(response.into())
}

View File

@ -85,23 +85,19 @@ impl DocumentWebSocketActor {
persistence,
});
match self.handle_revision(user, document_client_data).await {
Ok(_) => {},
Err(e) => {
tracing::error!("[DocumentWebSocketActor]: process client data error {:?}", e);
},
}
Ok(())
}
async fn handle_revision(&self, user: Arc<ServerDocUser>, client_data: DocumentClientWSData) -> Result<()> {
match &client_data.ty {
match &document_client_data.ty {
DocumentClientWSDataType::ClientPushRev => {
let _ = self
match self
.doc_manager
.apply_revisions(user, client_data)
.apply_revisions(user, document_client_data)
.await
.map_err(internal_error)?;
.map_err(internal_error)
{
Ok(_) => {},
Err(e) => {
tracing::error!("[DocumentWebSocketActor]: process client data failed: {:?}", e);
},
}
},
}

View File

@ -16,7 +16,7 @@ use flowy_collaboration::{
errors::CollaborateError,
protobuf::DocIdentifier,
};
use lib_infra::future::{BoxResultFuture, FutureResultSend};
use lib_infra::future::BoxResultFuture;
use flowy_collaboration::sync::{DocumentPersistence, ServerDocumentManager};
use std::{
@ -83,7 +83,7 @@ impl DocumentPersistence for DocumentPersistenceImpl {
doc_id: doc_id.to_string(),
..Default::default()
};
let persistence = self.0.clone();
let persistence = self.0.kv_store();
Box::pin(async move {
let mut pb_doc = read_document(&persistence, params)
.await
@ -115,11 +115,9 @@ impl DocumentPersistence for DocumentPersistenceImpl {
let kv_store = self.0.kv_store();
let doc_id = doc_id.to_owned();
let f = || async move {
let expected_len = rev_ids.len();
let mut pb = kv_store.batch_get_revisions(&doc_id, rev_ids).await?;
let repeated_revision: RepeatedRevision = (&mut pb).try_into()?;
let revisions = repeated_revision.into_inner();
assert_eq!(expected_len, revisions.len());
Ok(revisions)
};

View File

@ -1,13 +1,13 @@
use crate::{
services::kv::{KVStore, KVTransaction, KeyValue},
services::kv::{KVTransaction, KeyValue},
util::sqlx_ext::{map_sqlx_error, DBTransaction, SqlBuilder},
};
use anyhow::Context;
use async_trait::async_trait;
use backend_service::errors::ServerError;
use bytes::Bytes;
use futures_core::future::BoxFuture;
use lib_infra::future::{BoxResultFuture, FutureResultSend};
use lib_infra::future::BoxResultFuture;
use sql_builder::SqlBuilder as RawSqlBuilder;
use sqlx::{
postgres::{PgArguments, PgRow},
@ -17,7 +17,6 @@ use sqlx::{
Postgres,
Row,
};
use std::{future::Future, pin::Pin, sync::Arc};
const KV_TABLE: &str = "kv_table";
@ -26,6 +25,33 @@ pub struct PostgresKV {
}
impl PostgresKV {
pub async fn get(&self, key: &str) -> Result<Option<Bytes>, ServerError> {
let key = key.to_owned();
self.transaction(|mut transaction| Box::pin(async move { transaction.get(&key).await }))
.await
}
pub async fn set(&self, key: &str, value: Bytes) -> Result<(), ServerError> {
let key = key.to_owned();
self.transaction(|mut transaction| Box::pin(async move { transaction.set(&key, value).await }))
.await
}
pub async fn remove(&self, key: &str) -> Result<(), ServerError> {
let key = key.to_owned();
self.transaction(|mut transaction| Box::pin(async move { transaction.remove(&key).await }))
.await
}
pub async fn batch_set(&self, kvs: Vec<KeyValue>) -> Result<(), ServerError> {
self.transaction(|mut transaction| Box::pin(async move { transaction.batch_set(kvs).await }))
.await
}
pub async fn batch_get(&self, keys: Vec<String>) -> Result<Vec<KeyValue>, ServerError> {
self.transaction(|mut transaction| Box::pin(async move { transaction.batch_get(keys).await }))
.await
}
pub async fn transaction<F, O>(&self, f: F) -> Result<O, ServerError>
where
F: for<'a> FnOnce(Box<dyn KVTransaction + 'a>) -> BoxResultFuture<O, ServerError>,
@ -61,14 +87,13 @@ impl<'a, 'b> KVTransaction for PostgresTransaction<'a, 'b> {
.fetch_one(self.0 as &mut DBTransaction<'b>)
.await;
let result = match result {
match result {
Ok(val) => Ok(Some(Bytes::from(val.blob))),
Err(error) => match error {
Error::RowNotFound => Ok(None),
_ => Err(map_sqlx_error(error)),
},
};
result
}
}
async fn set(&mut self, key: &str, bytes: Bytes) -> Result<(), ServerError> {

View File

@ -3,12 +3,10 @@ mod kv;
use async_trait::async_trait;
use bytes::Bytes;
use futures_core::future::BoxFuture;
pub(crate) use kv::*;
use std::sync::Arc;
use backend_service::errors::ServerError;
use lib_infra::future::{BoxResultFuture, FutureResultSend};
// TODO: Generic the KVStore that enable switching KVStore to another
// implementation

View File

@ -50,3 +50,30 @@ async fn kv_batch_set_test() {
assert_eq!(kvs, kvs_from_db);
}
#[actix_rt::test]
async fn kv_batch_get_start_with_test() {
let server = spawn_server().await;
let kv = server.app_ctx.persistence.kv_store();
let kvs = vec![
KeyValue {
key: "abc:1".to_string(),
value: "a".to_string().into(),
},
KeyValue {
key: "abc:2".to_string(),
value: "b".to_string().into(),
},
];
kv.batch_set(kvs.clone()).await.unwrap();
kv.transaction(|mut transaction| {
Box::pin(async move {
let kvs_from_db = transaction.batch_get_start_with("abc").await.unwrap();
assert_eq!(kvs, kvs_from_db);
Ok(())
})
})
.await
.unwrap();
}

View File

@ -6,19 +6,22 @@ use flowy_document::services::doc::edit::ClientDocEditor as ClientEditDocContext
use flowy_test::{helper::ViewTest, FlowySDKTest};
use flowy_user::services::user::UserSession;
use futures_util::{stream, stream::StreamExt};
use sqlx::PgPool;
use std::sync::Arc;
use bytes::Bytes;
use tokio::time::{sleep, Duration};
// use crate::helper::*;
use crate::util::helper::{spawn_server, TestServer};
use flowy_collaboration::{entities::doc::DocIdentifier, protobuf::ResetDocumentParams};
use lib_ot::rich_text::{RichTextAttribute, RichTextDelta};
use parking_lot::RwLock;
use flowy_collaboration::entities::revision::RepeatedRevision;
use backend::services::document::persistence::{DocumentKVPersistence, read_document, reset_document};
use flowy_collaboration::entities::revision::{RepeatedRevision, Revision, RevType};
use lib_ot::core::Interval;
use flowy_net::services::ws::FlowyWSConnect;
use crate::util::helper::*;
pub struct DocumentTest {
server: TestServer,
@ -32,7 +35,7 @@ pub enum DocScript {
ClientOpenDoc,
AssertClient(&'static str),
AssertServer(&'static str, i64),
ServerSaveDocument(RepeatedRevision), // delta_json, rev_id
ServerSaveDocument(String, i64), // delta_json, rev_id
}
impl DocumentTest {
@ -78,10 +81,8 @@ impl ScriptContext {
}
async fn open_doc(&mut self) {
let flowy_document = self.client_sdk.flowy_document.clone();
let doc_id = self.doc_id.clone();
let edit_context = flowy_document.open(DocIdentifier { doc_id }).await.unwrap();
let edit_context = self.client_sdk.document_ctx.open(DocIdentifier { doc_id }).await.unwrap();
self.client_edit_context = Some(edit_context);
}
@ -106,6 +107,7 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
context.write().open_doc().await;
},
DocScript::ClientInsertText(index, s) => {
sleep(Duration::from_millis(2000)).await;
context.read().client_edit_context().insert(index, s).await.unwrap();
},
DocScript::ClientFormatText(interval, attribute) => {
@ -123,24 +125,32 @@ async fn run_scripts(context: Arc<RwLock<ScriptContext>>, scripts: Vec<DocScript
},
DocScript::AssertServer(s, rev_id) => {
sleep(Duration::from_millis(100)).await;
let persistence = Data::new(context.read().server.app_ctx.persistence.kv_store());
let doc_identifier: flowy_collaboration::protobuf::DocIdentifier = DocIdentifier {
doc_id
}.try_into().unwrap();
// let doc_identifier = DocIdentifier {
// doc_id
// };
//
// let doc = context.read().server.read_doc()
// let pg_pool = context.read().server_pg_pool.clone();
// let doc_manager = context.read().server_doc_manager.clone();
// let edit_doc = doc_manager.get(&doc_id).await.unwrap();
// let json = edit_doc.document_json().await.unwrap();
// assert_eq(s, &json);
// assert_eq!(edit_doc.rev_id().await.unwrap(), rev_id);
let document_info = read_document(persistence.get_ref(), doc_identifier).await.unwrap();
assert_eq(s, &document_info.text);
assert_eq!(document_info.rev_id, rev_id);
},
DocScript::ServerSaveDocument(repeated_revision) => {
let pg_pool = Data::new(context.read().server.pg_pool.clone());
reset_doc(&doc_id, repeated_revision, pg_pool).await;
DocScript::ServerSaveDocument(document_json, rev_id) => {
let delta_data = Bytes::from(document_json);
let user_id = context.read().client_user_session.user_id().unwrap();
let md5 = format!("{:x}", md5::compute(&delta_data));
let base_rev_id = if rev_id == 0 { rev_id } else { rev_id - 1 };
let revision = Revision::new(
&doc_id,
base_rev_id,
rev_id,
delta_data,
RevType::Remote,
&user_id,
md5,
);
let kv_store = Data::new(context.read().server.app_ctx.persistence.kv_store());
reset_doc(&doc_id, RepeatedRevision::new(vec![revision]), kv_store.get_ref()).await;
},
// DocScript::Sleep(sec) => {
// sleep(Duration::from_secs(sec)).await;
@ -174,10 +184,10 @@ async fn create_doc(flowy_test: &FlowySDKTest) -> String {
view_test.view.id
}
async fn reset_doc(doc_id: &str, repeated_revision: RepeatedRevision, pool: Data<PgPool>) {
async fn reset_doc(doc_id: &str, repeated_revision: RepeatedRevision, kv_store: &Arc<DocumentKVPersistence>) {
let pb: flowy_collaboration::protobuf::RepeatedRevision = repeated_revision.try_into().unwrap();
let mut params = ResetDocumentParams::new();
params.set_doc_id(doc_id.to_owned());
params.set_revisions(pb);
// let _ = reset_document_handler(pool.get_ref(), params).await.unwrap();
let _ = reset_document(kv_store, params).await.unwrap();
}

View File

@ -20,7 +20,6 @@ use lib_ot::{core::Interval, rich_text::RichTextAttribute};
async fn delta_sync_while_editing() {
let test = DocumentTest::new().await;
test.run_scripts(vec![
DocScript::ClientConnectWS,
DocScript::ClientOpenDoc,
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientInsertText(3, "123"),
@ -34,7 +33,6 @@ async fn delta_sync_while_editing() {
async fn delta_sync_multi_revs() {
let test = DocumentTest::new().await;
test.run_scripts(vec![
DocScript::ClientConnectWS,
DocScript::ClientOpenDoc,
DocScript::ClientInsertText(0, "abc"),
DocScript::ClientInsertText(3, "123"),
@ -81,6 +79,7 @@ async fn delta_sync_with_http_request() {
let mut document = Document::new::<FlowyDoc>();
document.insert(0, "123").unwrap();
document.insert(3, "456").unwrap();
let json = document.to_json();
test.run_scripts(vec![

View File

@ -1,5 +1,5 @@
use backend::{
application::{get_connection_pool, init_app_context, Application},
application::{init_app_context, Application},
config::{get_configuration, DatabaseSettings},
context::AppContext,
};
@ -9,10 +9,14 @@ use backend_service::{
user_request::*,
workspace_request::*,
};
use flowy_collaboration::entities::doc::{CreateDocParams, DocIdentifier, DocumentInfo};
use flowy_collaboration::{
document::default::initial_delta_string,
entities::doc::{CreateDocParams, DocIdentifier, DocumentInfo},
};
use flowy_core_data_model::entities::prelude::*;
use flowy_document::services::server::{create_doc_request, read_doc_request};
use flowy_user_data_model::entities::*;
use lib_infra::uuid_string;
use sqlx::{Connection, Executor, PgConnection, PgPool};
use uuid::Uuid;
@ -203,7 +207,6 @@ pub async fn spawn_user_server() -> TestUserServer {
#[derive(Clone)]
pub struct TestServer {
pub pg_pool: PgPool,
pub app_ctx: AppContext,
pub client_server_config: ClientServerConfiguration,
}
@ -234,9 +237,6 @@ pub async fn spawn_server() -> TestServer {
client_server_config.reset_host_with_port("localhost", application_port);
TestServer {
pg_pool: get_connection_pool(&configuration.database)
.await
.expect("Failed to connect to the database"),
app_ctx,
client_server_config,
}
@ -312,7 +312,15 @@ pub async fn create_test_view(application: &TestUserServer, app_id: &str) -> Vie
let desc = "This is my first view".to_string();
let thumbnail = "http://1.png".to_string();
let params = CreateViewParams::new(app_id.to_owned(), name, desc, ViewType::Doc, thumbnail);
let params = CreateViewParams::new(
app_id.to_owned(),
name,
desc,
ViewType::Doc,
thumbnail,
initial_delta_string(),
uuid_string(),
);
let app = application.create_view(params).await;
app
}

View File

@ -137,6 +137,8 @@ class CreateViewParams extends $pb.GeneratedMessage {
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'desc')
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'thumbnail')
..e<ViewType>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewType', $pb.PbFieldType.OE, defaultOrMaker: ViewType.Blank, valueOf: ViewType.valueOf, enumValues: ViewType.values)
..aOS(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewData')
..aOS(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'viewId')
..hasRequiredFields = false
;
@ -147,6 +149,8 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.String? desc,
$core.String? thumbnail,
ViewType? viewType,
$core.String? viewData,
$core.String? viewId,
}) {
final _result = create();
if (belongToId != null) {
@ -164,6 +168,12 @@ class CreateViewParams extends $pb.GeneratedMessage {
if (viewType != null) {
_result.viewType = viewType;
}
if (viewData != null) {
_result.viewData = viewData;
}
if (viewId != null) {
_result.viewId = viewId;
}
return _result;
}
factory CreateViewParams.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) => create()..mergeFromBuffer(i, r);
@ -231,6 +241,24 @@ class CreateViewParams extends $pb.GeneratedMessage {
$core.bool hasViewType() => $_has(4);
@$pb.TagNumber(5)
void clearViewType() => clearField(5);
@$pb.TagNumber(6)
$core.String get viewData => $_getSZ(5);
@$pb.TagNumber(6)
set viewData($core.String v) { $_setString(5, v); }
@$pb.TagNumber(6)
$core.bool hasViewData() => $_has(5);
@$pb.TagNumber(6)
void clearViewData() => clearField(6);
@$pb.TagNumber(7)
$core.String get viewId => $_getSZ(6);
@$pb.TagNumber(7)
set viewId($core.String v) { $_setString(6, v); }
@$pb.TagNumber(7)
$core.bool hasViewId() => $_has(6);
@$pb.TagNumber(7)
void clearViewId() => clearField(7);
}
class View extends $pb.GeneratedMessage {

View File

@ -45,11 +45,13 @@ const CreateViewParams$json = const {
const {'1': 'desc', '3': 3, '4': 1, '5': 9, '10': 'desc'},
const {'1': 'thumbnail', '3': 4, '4': 1, '5': 9, '10': 'thumbnail'},
const {'1': 'view_type', '3': 5, '4': 1, '5': 14, '6': '.ViewType', '10': 'viewType'},
const {'1': 'view_data', '3': 6, '4': 1, '5': 9, '10': 'viewData'},
const {'1': 'view_id', '3': 7, '4': 1, '5': 9, '10': 'viewId'},
],
};
/// Descriptor for `CreateViewParams`. Decode as a `google.protobuf.DescriptorProto`.
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBl');
final $typed_data.Uint8List createViewParamsDescriptor = $convert.base64Decode('ChBDcmVhdGVWaWV3UGFyYW1zEiAKDGJlbG9uZ190b19pZBgBIAEoCVIKYmVsb25nVG9JZBISCgRuYW1lGAIgASgJUgRuYW1lEhIKBGRlc2MYAyABKAlSBGRlc2MSHAoJdGh1bWJuYWlsGAQgASgJUgl0aHVtYm5haWwSJgoJdmlld190eXBlGAUgASgOMgkuVmlld1R5cGVSCHZpZXdUeXBlEhsKCXZpZXdfZGF0YRgGIAEoCVIIdmlld0RhdGESFwoHdmlld19pZBgHIAEoCVIGdmlld0lk');
@$core.Deprecated('Use viewDescriptor instead')
const View$json = const {
'1': 'View',

View File

@ -4,8 +4,8 @@ use chrono::Utc;
use lazy_static::lazy_static;
use parking_lot::RwLock;
use flowy_collaboration::{document::default::initial_read_me, entities::doc::DocumentDelta};
use flowy_core_data_model::user_default;
use flowy_collaboration::document::default::{initial_delta, initial_read_me};
use flowy_core_data_model::{entities::view::CreateViewParams, user_default};
use flowy_net::entities::NetworkType;
use crate::{
@ -85,24 +85,28 @@ impl CoreContext {
let apps = workspace.take_apps().into_inner();
let cloned_workspace = workspace.clone();
let _ = self.workspace_controller.create_workspace(workspace).await?;
let _ = self.workspace_controller.create_workspace_on_local(workspace).await?;
for mut app in apps {
let app_id = app.id.clone();
let views = app.take_belongings().into_inner();
let _ = self.app_controller.create_app(app).await?;
let _ = self.app_controller.create_app_on_local(app).await?;
for (index, view) in views.into_iter().enumerate() {
if index == 0 {
let delta = initial_read_me();
let doc_delta = DocumentDelta {
doc_id: view.id.clone(),
text: delta.to_json(),
};
let _ = self.view_controller.apply_doc_delta(doc_delta).await?;
self.view_controller.set_latest_view(&view);
// Close the view after initialize
self.view_controller.close_view(view.id.clone().into()).await?;
}
let _ = self.view_controller.create_view(view).await?;
let view_data = if index == 0 {
initial_read_me().to_json()
} else {
initial_delta().to_json()
};
self.view_controller.set_latest_view(&view);
let params = CreateViewParams {
belong_to_id: app_id.clone(),
name: view.name,
desc: view.desc,
thumbnail: "".to_string(),
view_type: view.view_type,
view_data,
view_id: view.id.clone(),
};
let _ = self.view_controller.create_view_from_params(params).await?;
}
}

View File

@ -40,17 +40,17 @@ impl AppController {
}
pub fn init(&self) -> Result<(), FlowyError> {
self.listen_trash_can_event();
self.listen_trash_controller_event();
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name) err)]
pub(crate) async fn create_app_from_params(&self, params: CreateAppParams) -> Result<App, FlowyError> {
let app = self.create_app_on_server(params).await?;
self.create_app(app).await
self.create_app_on_local(app).await
}
pub(crate) async fn create_app(&self, app: App) -> Result<App, FlowyError> {
pub(crate) async fn create_app_on_local(&self, app: App) -> Result<App, FlowyError> {
let conn = &*self.database.db_connection()?;
conn.immediate_transaction::<_, FlowyError, _>(|| {
let _ = self.save_app(app.clone(), &*conn)?;
@ -71,7 +71,7 @@ impl AppController {
let conn = self.database.db_connection()?;
let app_table = AppTableSql::read_app(&params.app_id, &*conn)?;
let trash_ids = self.trash_can.trash_ids(&conn)?;
let trash_ids = self.trash_can.read_trash_ids(&conn)?;
if trash_ids.contains(&app_table.id) {
return Err(FlowyError::record_not_found());
}
@ -165,7 +165,7 @@ impl AppController {
Ok(())
}
fn listen_trash_can_event(&self) {
fn listen_trash_controller_event(&self) {
let mut rx = self.trash_can.subscribe();
let database = self.database.clone();
let trash_can = self.trash_can.clone();
@ -245,56 +245,9 @@ pub fn read_local_workspace_apps(
conn: &SqliteConnection,
) -> Result<RepeatedApp, FlowyError> {
let mut app_tables = AppTableSql::read_workspace_apps(workspace_id, false, conn)?;
let trash_ids = trash_controller.trash_ids(conn)?;
let trash_ids = trash_controller.read_trash_ids(conn)?;
app_tables.retain(|app_table| !trash_ids.contains(&app_table.id));
let apps = app_tables.into_iter().map(|table| table.into()).collect::<Vec<App>>();
Ok(RepeatedApp { items: apps })
}
// #[tracing::instrument(level = "debug", skip(self), err)]
// pub(crate) async fn delete_app(&self, app_id: &str) -> Result<(),
// FlowyError> { let conn = &*self.database.db_connection()?;
// conn.immediate_transaction::<_, FlowyError, _>(|| {
// let app = AppTableSql::delete_app(app_id, &*conn)?;
// let apps = self.read_local_apps(&app.workspace_id, &*conn)?;
// send_dart_notification(&app.workspace_id,
// WorkspaceNotification::WorkspaceDeleteApp) .payload(apps)
// .send();
// Ok(())
// })?;
//
// let _ = self.delete_app_on_server(app_id);
// Ok(())
// }
//
// #[tracing::instrument(level = "debug", skip(self), err)]
// fn delete_app_on_server(&self, app_id: &str) -> Result<(), FlowyError> {
// let token = self.user.token()?;
// let server = self.server.clone();
// let params = DeleteAppParams {
// app_id: app_id.to_string(),
// };
// spawn(async move {
// match server.delete_app(&token, params).await {
// Ok(_) => {},
// Err(e) => {
// // TODO: retry?
// log::error!("Delete app failed: {:?}", e);
// },
// }
// });
// // let action = RetryAction::new(self.server.clone(), self.user.clone(),
// move // |token, server| { let params = params.clone();
// // async move {
// // match server.delete_app(&token, params).await {
// // Ok(_) => {},
// // Err(e) => log::error!("Delete app failed: {:?}", e),
// // }
// // Ok::<(), FlowyError>(())
// // }
// // });
// //
// // spawn_retry(500, 3, action);
// Ok(())
// }

View File

@ -29,17 +29,17 @@ pub(crate) async fn create_app_handler(
pub(crate) async fn delete_app_handler(
data: Data<QueryAppRequest>,
controller: Unit<Arc<AppController>>,
trash_can: Unit<Arc<TrashController>>,
view_controller: Unit<Arc<AppController>>,
trash_controller: Unit<Arc<TrashController>>,
) -> Result<(), FlowyError> {
let params: AppIdentifier = data.into_inner().try_into()?;
let trash = controller
let trash = view_controller
.read_app_tables(vec![params.app_id])?
.into_iter()
.map(|view_table| view_table.into())
.collect::<Vec<Trash>>();
let _ = trash_can.add(trash).await?;
let _ = trash_controller.add(trash).await?;
Ok(())
}

View File

@ -8,7 +8,7 @@ use crate::{
errors::FlowyError,
services::server::WorkspaceServerAPI,
};
use lib_infra::{future::FutureResult, timestamp, uuid};
use lib_infra::{future::FutureResult, timestamp, uuid_string};
pub struct WorkspaceServerMock {}
@ -18,7 +18,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
fn create_workspace(&self, _token: &str, params: CreateWorkspaceParams) -> FutureResult<Workspace, FlowyError> {
let time = timestamp();
let workspace = Workspace {
id: uuid(),
id: uuid_string(),
name: params.name,
desc: params.desc,
apps: RepeatedApp::default(),
@ -51,7 +51,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
fn create_view(&self, _token: &str, params: CreateViewParams) -> FutureResult<View, FlowyError> {
let time = timestamp();
let view = View {
id: uuid(),
id: uuid_string(),
belong_to_id: params.belong_to_id,
name: params.name,
desc: params.desc,
@ -79,7 +79,7 @@ impl WorkspaceServerAPI for WorkspaceServerMock {
fn create_app(&self, _token: &str, params: CreateAppParams) -> FutureResult<App, FlowyError> {
let time = timestamp();
let app = App {
id: uuid(),
id: uuid_string(),
workspace_id: params.workspace_id,
name: params.name,
desc: params.desc,

View File

@ -182,7 +182,7 @@ impl TrashController {
Ok(repeated_trash)
}
pub fn trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, FlowyError> {
pub fn read_trash_ids(&self, conn: &SqliteConnection) -> Result<Vec<String>, FlowyError> {
let ids = TrashTableSql::read_all(&*conn)?
.into_inner()
.into_iter()

View File

@ -21,6 +21,7 @@ use crate::{
use flowy_core_data_model::entities::share::{ExportData, ExportParams};
use flowy_database::kv::KV;
use flowy_document::context::DocumentContext;
use lib_infra::uuid_string;
const LATEST_VIEW_ID: &str = "latest_view_id";
@ -57,11 +58,11 @@ impl ViewController {
#[tracing::instrument(level = "debug", skip(self, params), fields(name = %params.name), err)]
pub(crate) async fn create_view_from_params(&self, params: CreateViewParams) -> Result<View, FlowyError> {
let view = self.create_view_on_server(params.clone()).await?;
self.create_view(view).await
let view = self.create_view_on_server(params).await?;
self.create_view_on_local(view).await
}
pub(crate) async fn create_view(&self, view: View) -> Result<View, FlowyError> {
pub(crate) async fn create_view_on_local(&self, view: View) -> Result<View, FlowyError> {
let conn = &*self.database.db_connection()?;
let trash_can = self.trash_can.clone();
@ -86,7 +87,7 @@ impl ViewController {
let conn = self.database.db_connection()?;
let view_table = ViewTableSql::read_view(&params.view_id, &*conn)?;
let trash_ids = self.trash_can.trash_ids(&conn)?;
let trash_ids = self.trash_can.read_trash_ids(&conn)?;
if trash_ids.contains(&view_table.id) {
return Err(FlowyError::record_not_found());
}
@ -120,7 +121,7 @@ impl ViewController {
#[tracing::instrument(level = "debug", skip(self,params), fields(doc_id = %params.doc_id), err)]
pub(crate) async fn close_view(&self, params: DocIdentifier) -> Result<(), FlowyError> {
let _ = self.document_ctx.close(params).await?;
let _ = self.document_ctx.doc_ctrl.close(&params.doc_id)?;
Ok(())
}
@ -131,14 +132,14 @@ impl ViewController {
let _ = KV::remove(LATEST_VIEW_ID);
}
}
let _ = self.document_ctx.close(params).await?;
let _ = self.document_ctx.doc_ctrl.close(&params.doc_id)?;
Ok(())
}
#[tracing::instrument(level = "debug", skip(self, params), fields(doc_id = %params.doc_id), err)]
pub(crate) async fn duplicate_view(&self, params: DocIdentifier) -> Result<(), FlowyError> {
let view: View = ViewTableSql::read_view(&params.doc_id, &*self.database.db_connection()?)?.into();
let _delta_data = self
let delta_data = self
.document_ctx
.read_document_data(params, self.database.db_pool()?)
.await?;
@ -149,6 +150,8 @@ impl ViewController {
desc: view.desc.clone(),
thumbnail: "".to_owned(),
view_type: view.view_type.clone(),
view_data: delta_data.text,
view_id: uuid_string(),
};
let _ = self.create_view_from_params(duplicate_params).await?;
@ -174,7 +177,7 @@ impl ViewController {
pub(crate) async fn read_views_belong_to(&self, belong_to_id: &str) -> Result<RepeatedView, FlowyError> {
// TODO: read from server
let conn = self.database.db_connection()?;
let repeated_view = read_local_belonging_view(belong_to_id, self.trash_can.clone(), &conn)?;
let repeated_view = read_belonging_views_on_local(belong_to_id, self.trash_can.clone(), &conn)?;
Ok(repeated_view)
}
@ -195,13 +198,13 @@ impl ViewController {
//
let _ = notify_views_changed(&updated_view.belong_to_id, self.trash_can.clone(), conn)?;
let _ = self.update_view_on_server(params);
Ok(updated_view)
}
pub(crate) async fn apply_doc_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
let doc = self.document_ctx.apply_doc_delta(params).await?;
let db_pool = self.document_ctx.user.db_pool()?;
let doc = self.document_ctx.doc_ctrl.apply_local_delta(params, db_pool).await?;
Ok(doc)
}
@ -308,7 +311,7 @@ async fn handle_trash_event(
TrashEvent::NewTrash(identifiers, ret) => {
let result = || {
let conn = &*db_result?;
let view_tables = get_view_table_from(identifiers, conn)?;
let view_tables = read_view_tables(identifiers, conn)?;
for view_table in view_tables {
let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
notify_dart(view_table, WorkspaceNotification::ViewDeleted);
@ -320,7 +323,7 @@ async fn handle_trash_event(
TrashEvent::Putback(identifiers, ret) => {
let result = || {
let conn = &*db_result?;
let view_tables = get_view_table_from(identifiers, conn)?;
let view_tables = read_view_tables(identifiers, conn)?;
for view_table in view_tables {
let _ = notify_views_changed(&view_table.belong_to_id, trash_can.clone(), conn)?;
notify_dart(view_table, WorkspaceNotification::ViewRestored);
@ -337,7 +340,7 @@ async fn handle_trash_event(
for identifier in identifiers.items {
let view_table = ViewTableSql::read_view(&identifier.id, conn)?;
let _ = ViewTableSql::delete_view(&identifier.id, conn)?;
let _ = context.delete(identifier.id.clone().into())?;
let _ = context.doc_ctrl.delete(identifier.id.clone().into())?;
notify_ids.insert(view_table.belong_to_id);
}
@ -354,7 +357,7 @@ async fn handle_trash_event(
}
}
fn get_view_table_from(identifiers: TrashIdentifiers, conn: &SqliteConnection) -> Result<Vec<ViewTable>, FlowyError> {
fn read_view_tables(identifiers: TrashIdentifiers, conn: &SqliteConnection) -> Result<Vec<ViewTable>, FlowyError> {
let mut view_tables = vec![];
let _ = conn.immediate_transaction::<_, FlowyError, _>(|| {
for identifier in identifiers.items {
@ -377,7 +380,7 @@ fn notify_views_changed(
trash_can: Arc<TrashController>,
conn: &SqliteConnection,
) -> FlowyResult<()> {
let repeated_view = read_local_belonging_view(belong_to_id, trash_can.clone(), conn)?;
let repeated_view = read_belonging_views_on_local(belong_to_id, trash_can.clone(), conn)?;
tracing::Span::current().record("view_count", &format!("{}", repeated_view.len()).as_str());
send_dart_notification(&belong_to_id, WorkspaceNotification::AppViewsChanged)
.payload(repeated_view)
@ -385,13 +388,13 @@ fn notify_views_changed(
Ok(())
}
fn read_local_belonging_view(
fn read_belonging_views_on_local(
belong_to_id: &str,
trash_can: Arc<TrashController>,
trash_controller: Arc<TrashController>,
conn: &SqliteConnection,
) -> FlowyResult<RepeatedView> {
let mut view_tables = ViewTableSql::read_views(belong_to_id, conn)?;
let trash_ids = trash_can.trash_ids(conn)?;
let trash_ids = trash_controller.read_trash_ids(conn)?;
view_tables.retain(|view_table| !trash_ids.contains(&view_table.id));
let views = view_tables

View File

@ -62,21 +62,21 @@ pub(crate) async fn apply_doc_delta_handler(
pub(crate) async fn delete_view_handler(
data: Data<QueryViewRequest>,
controller: Unit<Arc<ViewController>>,
trash_can: Unit<Arc<TrashController>>,
view_controller: Unit<Arc<ViewController>>,
trash_controller: Unit<Arc<TrashController>>,
) -> Result<(), FlowyError> {
let params: ViewIdentifiers = data.into_inner().try_into()?;
for view_id in &params.view_ids {
let _ = controller.delete_view(view_id.into()).await;
let _ = view_controller.delete_view(view_id.into()).await;
}
let trash = controller
let trash = view_controller
.read_view_tables(params.view_ids)?
.into_iter()
.map(|view_table| view_table.into())
.collect::<Vec<Trash>>();
let _ = trash_can.add(trash).await?;
let _ = trash_controller.add(trash).await?;
Ok(())
}

View File

@ -42,10 +42,10 @@ impl WorkspaceController {
params: CreateWorkspaceParams,
) -> Result<Workspace, FlowyError> {
let workspace = self.create_workspace_on_server(params.clone()).await?;
self.create_workspace(workspace).await
self.create_workspace_on_local(workspace).await
}
pub(crate) async fn create_workspace(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
pub(crate) async fn create_workspace_on_local(&self, workspace: Workspace) -> Result<Workspace, FlowyError> {
let user_id = self.user.user_id()?;
let token = self.user.token()?;
let workspace_table = WorkspaceTable::new(workspace.clone(), &user_id);

View File

@ -19,8 +19,8 @@ pub trait DocumentUser: Send + Sync {
}
pub struct DocumentContext {
doc_ctrl: Arc<DocController>,
user: Arc<dyn DocumentUser>,
pub doc_ctrl: Arc<DocController>,
pub user: Arc<dyn DocumentUser>,
}
impl DocumentContext {
@ -40,21 +40,11 @@ impl DocumentContext {
Ok(())
}
pub fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
let _ = self.doc_ctrl.delete(params)?;
Ok(())
}
pub async fn open(&self, params: DocIdentifier) -> Result<Arc<ClientDocEditor>, FlowyError> {
let edit_context = self.doc_ctrl.open(params, self.user.db_pool()?).await?;
Ok(edit_context)
}
pub async fn close(&self, params: DocIdentifier) -> Result<(), FlowyError> {
let _ = self.doc_ctrl.close(&params.doc_id)?;
Ok(())
}
pub async fn read_document_data(
&self,
params: DocIdentifier,
@ -64,14 +54,4 @@ impl DocumentContext {
let delta = edit_context.delta().await?;
Ok(delta)
}
pub async fn apply_doc_delta(&self, params: DocumentDelta) -> Result<DocumentDelta, FlowyError> {
// workaround: compare the rust's delta with flutter's delta. Will be removed
// very soon
let doc = self
.doc_ctrl
.apply_local_delta(params.clone(), self.user.db_pool()?)
.await?;
Ok(doc)
}
}

View File

@ -20,7 +20,7 @@ use flowy_error::FlowyResult;
use lib_infra::future::FutureResult;
use std::sync::Arc;
pub(crate) struct DocController {
pub struct DocController {
server: Server,
ws_receivers: Arc<DocumentWSReceivers>,
ws_sender: Arc<dyn DocumentWebSocket>,
@ -52,7 +52,7 @@ impl DocController {
Ok(())
}
pub(crate) async fn open(
pub async fn open(
&self,
params: DocIdentifier,
pool: Arc<ConnectionPool>,
@ -66,7 +66,7 @@ impl DocController {
Ok(edit_doc_ctx)
}
pub(crate) fn close(&self, doc_id: &str) -> Result<(), FlowyError> {
pub fn close(&self, doc_id: &str) -> Result<(), FlowyError> {
tracing::debug!("Close document {}", doc_id);
self.open_cache.remove(doc_id);
self.ws_receivers.remove_receiver(doc_id);
@ -74,7 +74,7 @@ impl DocController {
}
#[tracing::instrument(level = "debug", skip(self), err)]
pub(crate) fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
pub fn delete(&self, params: DocIdentifier) -> Result<(), FlowyError> {
let doc_id = &params.doc_id;
self.open_cache.remove(doc_id);
self.ws_receivers.remove_receiver(doc_id);
@ -86,7 +86,7 @@ impl DocController {
// json : {"retain":7,"attributes":{"bold":null}}
// deserialize delta: [ {retain: 7, attributes: {Bold: AttributeValue(None)}} ]
#[tracing::instrument(level = "debug", skip(self, delta, db_pool), fields(doc_id = %delta.doc_id), err)]
pub(crate) async fn apply_local_delta(
pub async fn apply_local_delta(
&self,
delta: DocumentDelta,
db_pool: Arc<ConnectionPool>,

View File

@ -70,7 +70,7 @@ pub struct FlowySDK {
#[allow(dead_code)]
config: FlowySDKConfig,
pub user_session: Arc<UserSession>,
pub flowy_document: Arc<DocumentContext>,
pub document_ctx: Arc<DocumentContext>,
pub core: Arc<CoreContext>,
pub dispatcher: Arc<EventDispatcher>,
pub ws_manager: Arc<FlowyWSConnect>,
@ -101,7 +101,7 @@ impl FlowySDK {
Self {
config,
user_session,
flowy_document,
document_ctx: flowy_document,
core: core_ctx,
dispatcher,
ws_manager,

View File

@ -31,7 +31,7 @@ impl EditorTest {
let _ = sdk.init_user().await;
let test = ViewTest::new(&sdk).await;
let doc_identifier: DocIdentifier = test.view.id.clone().into();
let editor = sdk.flowy_document.open(doc_identifier).await.unwrap();
let editor = sdk.document_ctx.open(doc_identifier).await.unwrap();
Self { sdk, editor }
}

View File

@ -17,7 +17,7 @@ use flowy_user::{
event::UserEvent::{InitUser, SignIn, SignOut, SignUp},
};
use lib_dispatch::prelude::{EventDispatcher, ModuleRequest, ToBytes};
use lib_infra::uuid;
use lib_infra::uuid_string;
use crate::prelude::*;
@ -293,7 +293,7 @@ pub fn root_dir() -> String {
root_dir
}
pub fn random_email() -> String { format!("{}@appflowy.io", uuid()) }
pub fn random_email() -> String { format!("{}@appflowy.io", uuid_string()) }
pub fn login_email() -> String { "annie2@appflowy.io".to_string() }

View File

@ -6,7 +6,7 @@ use crate::helper::*;
use backend_service::configuration::{get_client_server_configuration, ClientServerConfiguration};
use flowy_sdk::{FlowySDK, FlowySDKConfig};
use flowy_user::entities::UserProfile;
use lib_infra::uuid;
use lib_infra::uuid_string;
pub mod prelude {
pub use crate::{event_builder::*, helper::*, *};
@ -31,7 +31,7 @@ impl FlowySDKTest {
}
pub fn setup_with(server_config: ClientServerConfiguration) -> Self {
let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid()).log_filter("debug");
let config = FlowySDKConfig::new(&root_dir(), server_config, &uuid_string()).log_filter("debug");
let sdk = FlowySDK::new(config);
Self(sdk)
}

View File

@ -4,7 +4,7 @@ use crate::{
};
use crate::services::server::UserServerAPI;
use lib_infra::{future::FutureResult, uuid};
use lib_infra::{future::FutureResult, uuid_string};
pub struct UserServerMock {}
@ -12,7 +12,7 @@ impl UserServerMock {}
impl UserServerAPI for UserServerMock {
fn sign_up(&self, params: SignUpParams) -> FutureResult<SignUpResponse, FlowyError> {
let uid = uuid();
let uid = uuid_string();
FutureResult::new(async move {
Ok(SignUpResponse {
user_id: uid.clone(),
@ -24,7 +24,7 @@ impl UserServerAPI for UserServerMock {
}
fn sign_in(&self, params: SignInParams) -> FutureResult<SignInResponse, FlowyError> {
let user_id = uuid();
let user_id = uuid_string();
FutureResult::new(async {
Ok(SignInResponse {
user_id: user_id.clone(),

View File

@ -1,7 +1,7 @@
use crate::helper::*;
use flowy_test::{event_builder::UserModuleEventBuilder, FlowySDKTest};
use flowy_user::{errors::ErrorCode, event::UserEvent::*, prelude::*};
use lib_infra::uuid;
use lib_infra::uuid_string;
use serial_test::*;
#[tokio::test]
@ -53,7 +53,7 @@ async fn user_update_with_name() {
async fn user_update_with_email() {
let sdk = FlowySDKTest::setup();
let user = sdk.init_user().await;
let new_email = format!("{}@gmail.com", uuid());
let new_email = format!("{}@gmail.com", uuid_string());
let request = UpdateUserRequest::new(&user.id).email(&new_email);
let _ = UserModuleEventBuilder::new(sdk.clone())
.event(UpdateUser)

View File

@ -2,7 +2,7 @@ use bytes::Bytes;
use dashmap::DashMap;
use flowy_collaboration::{entities::prelude::*, errors::CollaborateError, sync::*};
// use flowy_net::services::ws::*;
use lib_infra::future::{BoxResultFuture, FutureResultSend};
use lib_infra::future::BoxResultFuture;
use lib_ws::{WSModule, WebSocketRawMessage};
use std::{
convert::TryInto,

View File

@ -54,7 +54,7 @@ impl ClientServerConfiguration {
pub fn view_url(&self) -> String { format!("{}/api/view", self.base_url()) }
pub fn doc_url(&self) -> String { format!("{}/api/document", self.base_url()) }
pub fn doc_url(&self) -> String { format!("{}/api/doc", self.base_url()) }
pub fn trash_url(&self) -> String { format!("{}/api/trash", self.base_url()) }

View File

@ -1,6 +1,6 @@
use crate::{
entities::revision::{RepeatedRevision, Revision},
errors::{internal_error, CollaborateError},
errors::CollaborateError,
};
use flowy_derive::ProtoBuf;
use lib_ot::{core::OperationTransformable, errors::OTError, rich_text::RichTextDelta};
@ -43,8 +43,11 @@ impl DocumentInfo {
for revision in revisions {
base_rev_id = revision.base_rev_id;
rev_id = revision.rev_id;
let delta = RichTextDelta::from_bytes(revision.delta_data).map_err(internal_error)?;
document_delta = document_delta.compose(&delta).map_err(internal_error)?;
let delta = RichTextDelta::from_bytes(revision.delta_data)
.map_err(|e| CollaborateError::internal().context(format!("Parser revision failed. {:?}", e)))?;
document_delta = document_delta
.compose(&delta)
.map_err(|e| CollaborateError::internal().context(format!("Compose delta failed. {:?}", e)))?;
}
let text = document_delta.to_json();

View File

@ -1,4 +1,3 @@
use crate::document::default::initial_delta;
use bytes::Bytes;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use lib_ot::rich_text::RichTextDelta;
@ -43,12 +42,6 @@ impl Revision {
#[allow(dead_code)]
pub fn is_initial(&self) -> bool { self.rev_id == 0 }
pub fn initial_revision(user_id: &str, doc_id: &str, ty: RevType) -> Self {
let delta_data = initial_delta().to_bytes();
let md5 = format!("{:x}", md5::compute(&delta_data));
Revision::new(doc_id, 0, 0, delta_data, ty, user_id, md5)
}
pub fn new(
doc_id: &str,
base_rev_id: i64,

View File

@ -12,7 +12,7 @@ use crate::{
use async_stream::stream;
use dashmap::DashMap;
use futures::stream::StreamExt;
use lib_infra::future::{BoxResultFuture, FutureResultSend};
use lib_infra::future::BoxResultFuture;
use lib_ot::rich_text::RichTextDelta;
use std::{convert::TryFrom, fmt::Debug, sync::Arc};
use tokio::{
@ -62,14 +62,13 @@ impl ServerDocumentManager {
let result = match self.get_document_handler(&doc_id).await {
None => {
let _ = self.create_document(&doc_id, revisions).await.map_err(internal_error)?;
let _ = self.create_document(&doc_id, revisions).await.map_err(|e| {
CollaborateError::internal().context(format!("Server crate document failed: {}", e))
})?;
Ok(())
},
Some(handler) => {
let _ = handler
.apply_revisions(doc_id.clone(), user, revisions)
.await
.map_err(internal_error)?;
let _ = handler.apply_revisions(doc_id.clone(), user, revisions).await?;
Ok(())
},
};
@ -102,6 +101,7 @@ impl ServerDocumentManager {
}
}
#[tracing::instrument(level = "debug", skip(self, revisions), err)]
async fn create_document(
&self,
doc_id: &str,
@ -117,7 +117,7 @@ impl ServerDocumentManager {
let persistence = self.persistence.clone();
let handle = spawn_blocking(|| OpenDocHandle::new(doc, persistence))
.await
.map_err(internal_error)?;
.map_err(|e| CollaborateError::internal().context(format!("Create open doc handler failed: {}", e)))?;
let handle = Arc::new(handle?);
self.open_doc_map.insert(doc_id, handle.clone());
Ok(handle)
@ -125,6 +125,7 @@ impl ServerDocumentManager {
}
struct OpenDocHandle {
doc_id: String,
sender: mpsc::Sender<DocumentCommand>,
persistence: Arc<dyn DocumentPersistence>,
users: DashMap<String, Arc<dyn RevisionUser>>,
@ -132,17 +133,20 @@ struct OpenDocHandle {
impl OpenDocHandle {
fn new(doc: DocumentInfo, persistence: Arc<dyn DocumentPersistence>) -> Result<Self, CollaborateError> {
let doc_id = doc.doc_id.clone();
let (sender, receiver) = mpsc::channel(100);
let users = DashMap::new();
let queue = DocumentCommandQueue::new(receiver, doc)?;
tokio::task::spawn(queue.run());
Ok(Self {
doc_id,
sender,
persistence,
users,
})
}
#[tracing::instrument(level = "debug", skip(self, user, revisions), err)]
async fn apply_revisions(
&self,
doc_id: String,
@ -159,18 +163,28 @@ impl OpenDocHandle {
persistence,
ret,
};
let _ = self.send(msg, rx).await?;
Ok(())
}
async fn send<T>(&self, msg: DocumentCommand, rx: oneshot::Receiver<T>) -> CollaborateResult<T> {
let _ = self.sender.send(msg).await.map_err(internal_error)?;
let result = rx.await.map_err(internal_error)?;
Ok(result)
let _ = self
.sender
.send(msg)
.await
.map_err(|e| CollaborateError::internal().context(format!("Send document command failed: {}", e)))?;
Ok(rx.await.map_err(internal_error)?)
}
}
#[derive(Debug)]
impl std::ops::Drop for OpenDocHandle {
fn drop(&mut self) {
log::debug!("{} OpenDocHandle drop", self.doc_id);
}
}
// #[derive(Debug)]
enum DocumentCommand {
ApplyRevisions {
doc_id: String,
@ -229,12 +243,20 @@ impl DocumentCommandQueue {
persistence,
ret,
} => {
self.synchronizer
.apply_revisions(doc_id, user, revisions, persistence)
let result = self
.synchronizer
.sync_revisions(doc_id, user, revisions, persistence)
.await
.unwrap();
let _ = ret.send(Ok(()));
.map_err(internal_error);
log::debug!("handle message {:?}", result);
let _ = ret.send(result);
},
}
}
}
impl std::ops::Drop for DocumentCommandQueue {
fn drop(&mut self) {
log::debug!("{} DocumentCommandQueue drop", self.doc_id);
}
}

View File

@ -4,10 +4,11 @@ use crate::{
revision::{Revision, RevisionRange},
ws::{DocumentServerWSData, DocumentServerWSDataBuilder},
},
errors::CollaborateError,
sync::DocumentPersistence,
};
use futures::TryFutureExt;
use lib_ot::{core::OperationTransformable, errors::OTError, rich_text::RichTextDelta};
use lib_ot::{core::OperationTransformable, rich_text::RichTextDelta};
use parking_lot::RwLock;
use std::{
cmp::Ordering,
@ -48,19 +49,16 @@ impl RevisionSynchronizer {
}
#[tracing::instrument(level = "debug", skip(self, user, revisions, persistence), err)]
pub async fn apply_revisions(
pub async fn sync_revisions(
&self,
doc_id: String,
user: Arc<dyn RevisionUser>,
revisions: Vec<Revision>,
persistence: Arc<dyn DocumentPersistence>,
) -> Result<(), OTError> {
) -> Result<(), CollaborateError> {
if revisions.is_empty() {
// Return all the revisions to client
let revisions = persistence
.get_doc_revisions(&doc_id)
.map_err(|e| OTError::internal().context(e))
.await?;
let revisions = persistence.get_doc_revisions(&doc_id).await?;
let data = DocumentServerWSDataBuilder::build_push_message(&doc_id, revisions);
user.receive(SyncResponse::Push(data));
return Ok(());
@ -78,10 +76,8 @@ impl RevisionSynchronizer {
let server_rev_id = next(server_base_rev_id);
if server_base_rev_id == first_revision.base_rev_id || server_rev_id == first_revision.rev_id {
// The rev is in the right order, just compose it.
{
for revision in &revisions {
let _ = self.compose_revision(revision)?;
}
for revision in &revisions {
let _ = self.compose_revision(revision)?;
}
user.receive(SyncResponse::NewRevision(revisions));
} else {
@ -108,7 +104,11 @@ impl RevisionSynchronizer {
let rev_ids: Vec<i64> = (from_rev_id..=to_rev_id).collect();
let revisions = match persistence.get_revisions(&self.doc_id, rev_ids).await {
Ok(revisions) => {
assert_eq!(revisions.is_empty(), false);
assert_eq!(
revisions.is_empty(),
false,
"revisions should not be empty if the doc exists"
);
revisions
},
Err(e) => {
@ -126,7 +126,7 @@ impl RevisionSynchronizer {
pub fn doc_json(&self) -> String { self.document.read().to_json() }
fn compose_revision(&self, revision: &Revision) -> Result<(), OTError> {
fn compose_revision(&self, revision: &Revision) -> Result<(), CollaborateError> {
let delta = RichTextDelta::from_bytes(&revision.delta_data)?;
let _ = self.compose_delta(delta)?;
let _ = self.rev_id.fetch_update(SeqCst, SeqCst, |_e| Some(revision.rev_id));
@ -134,13 +134,13 @@ impl RevisionSynchronizer {
}
#[tracing::instrument(level = "debug", skip(self, revision))]
fn transform_revision(&self, revision: &Revision) -> Result<(RichTextDelta, RichTextDelta), OTError> {
fn transform_revision(&self, revision: &Revision) -> Result<(RichTextDelta, RichTextDelta), CollaborateError> {
let cli_delta = RichTextDelta::from_bytes(&revision.delta_data)?;
let result = self.document.read().delta().transform(&cli_delta)?;
Ok(result)
}
fn compose_delta(&self, delta: RichTextDelta) -> Result<(), OTError> {
fn compose_delta(&self, delta: RichTextDelta) -> Result<(), CollaborateError> {
if delta.is_empty() {
log::warn!("Composed delta is empty");
}

View File

@ -7,7 +7,7 @@ use crate::{
view::{ViewName, ViewThumbnail},
},
};
use flowy_collaboration::document::default::initial_delta_string;
use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use std::convert::TryInto;
@ -68,16 +68,32 @@ pub struct CreateViewParams {
#[pb(index = 5)]
pub view_type: ViewType,
#[pb(index = 6)]
pub view_data: String,
#[pb(index = 7)]
pub view_id: String,
}
impl CreateViewParams {
pub fn new(belong_to_id: String, name: String, desc: String, view_type: ViewType, thumbnail: String) -> Self {
pub fn new(
belong_to_id: String,
name: String,
desc: String,
view_type: ViewType,
thumbnail: String,
view_data: String,
view_id: String,
) -> Self {
Self {
belong_to_id,
name,
desc,
thumbnail,
view_type,
view_data,
view_id,
}
}
}
@ -88,7 +104,8 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
fn try_into(self) -> Result<CreateViewParams, Self::Error> {
let name = ViewName::parse(self.name)?.0;
let belong_to_id = AppId::parse(self.belong_to_id)?.0;
let view_data = initial_delta_string();
let view_id = uuid::Uuid::new_v4().to_string();
let thumbnail = match self.thumbnail {
None => "".to_string(),
Some(thumbnail) => ViewThumbnail::parse(thumbnail)?.0,
@ -100,6 +117,8 @@ impl TryInto<CreateViewParams> for CreateViewRequest {
self.desc,
self.view_type,
thumbnail,
view_data,
view_id,
))
}
}

View File

@ -387,6 +387,8 @@ pub struct CreateViewParams {
pub desc: ::std::string::String,
pub thumbnail: ::std::string::String,
pub view_type: ViewType,
pub view_data: ::std::string::String,
pub view_id: ::std::string::String,
// special fields
pub unknown_fields: ::protobuf::UnknownFields,
pub cached_size: ::protobuf::CachedSize,
@ -521,6 +523,58 @@ impl CreateViewParams {
pub fn set_view_type(&mut self, v: ViewType) {
self.view_type = v;
}
// string view_data = 6;
pub fn get_view_data(&self) -> &str {
&self.view_data
}
pub fn clear_view_data(&mut self) {
self.view_data.clear();
}
// Param is passed by value, moved
pub fn set_view_data(&mut self, v: ::std::string::String) {
self.view_data = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_view_data(&mut self) -> &mut ::std::string::String {
&mut self.view_data
}
// Take field
pub fn take_view_data(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.view_data, ::std::string::String::new())
}
// string view_id = 7;
pub fn get_view_id(&self) -> &str {
&self.view_id
}
pub fn clear_view_id(&mut self) {
self.view_id.clear();
}
// Param is passed by value, moved
pub fn set_view_id(&mut self, v: ::std::string::String) {
self.view_id = v;
}
// Mutable pointer to the field.
// If field is not initialized, it is initialized with default value first.
pub fn mut_view_id(&mut self) -> &mut ::std::string::String {
&mut self.view_id
}
// Take field
pub fn take_view_id(&mut self) -> ::std::string::String {
::std::mem::replace(&mut self.view_id, ::std::string::String::new())
}
}
impl ::protobuf::Message for CreateViewParams {
@ -547,6 +601,12 @@ impl ::protobuf::Message for CreateViewParams {
5 => {
::protobuf::rt::read_proto3_enum_with_unknown_fields_into(wire_type, is, &mut self.view_type, 5, &mut self.unknown_fields)?
},
6 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_data)?;
},
7 => {
::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.view_id)?;
},
_ => {
::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
},
@ -574,6 +634,12 @@ impl ::protobuf::Message for CreateViewParams {
if self.view_type != ViewType::Blank {
my_size += ::protobuf::rt::enum_size(5, self.view_type);
}
if !self.view_data.is_empty() {
my_size += ::protobuf::rt::string_size(6, &self.view_data);
}
if !self.view_id.is_empty() {
my_size += ::protobuf::rt::string_size(7, &self.view_id);
}
my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
self.cached_size.set(my_size);
my_size
@ -595,6 +661,12 @@ impl ::protobuf::Message for CreateViewParams {
if self.view_type != ViewType::Blank {
os.write_enum(5, ::protobuf::ProtobufEnum::value(&self.view_type))?;
}
if !self.view_data.is_empty() {
os.write_string(6, &self.view_data)?;
}
if !self.view_id.is_empty() {
os.write_string(7, &self.view_id)?;
}
os.write_unknown_fields(self.get_unknown_fields())?;
::std::result::Result::Ok(())
}
@ -658,6 +730,16 @@ impl ::protobuf::Message for CreateViewParams {
|m: &CreateViewParams| { &m.view_type },
|m: &mut CreateViewParams| { &mut m.view_type },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"view_data",
|m: &CreateViewParams| { &m.view_data },
|m: &mut CreateViewParams| { &mut m.view_data },
));
fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
"view_id",
|m: &CreateViewParams| { &m.view_id },
|m: &mut CreateViewParams| { &mut m.view_id },
));
::protobuf::reflect::MessageDescriptor::new_pb_name::<CreateViewParams>(
"CreateViewParams",
fields,
@ -679,6 +761,8 @@ impl ::protobuf::Clear for CreateViewParams {
self.desc.clear();
self.thumbnail.clear();
self.view_type = ViewType::Blank;
self.view_data.clear();
self.view_id.clear();
self.unknown_fields.clear();
}
}
@ -1394,88 +1478,95 @@ static file_descriptor_proto_data: &'static [u8] = b"\
long_to_id\x18\x01\x20\x01(\tR\nbelongToId\x12\x12\n\x04name\x18\x02\x20\
\x01(\tR\x04name\x12\x12\n\x04desc\x18\x03\x20\x01(\tR\x04desc\x12\x1e\n\
\tthumbnail\x18\x04\x20\x01(\tH\0R\tthumbnail\x12&\n\tview_type\x18\x05\
\x20\x01(\x0e2\t.ViewTypeR\x08viewTypeB\x12\n\x10one_of_thumbnail\"\xa2\
\x20\x01(\x0e2\t.ViewTypeR\x08viewTypeB\x12\n\x10one_of_thumbnail\"\xd8\
\x01\n\x10CreateViewParams\x12\x20\n\x0cbelong_to_id\x18\x01\x20\x01(\tR\
\nbelongToId\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04\
desc\x18\x03\x20\x01(\tR\x04desc\x12\x1c\n\tthumbnail\x18\x04\x20\x01(\t\
R\tthumbnail\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\t.ViewTypeR\x08view\
Type\"\x97\x02\n\x04View\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\
\x20\n\x0cbelong_to_id\x18\x02\x20\x01(\tR\nbelongToId\x12\x12\n\x04name\
\x18\x03\x20\x01(\tR\x04name\x12\x12\n\x04desc\x18\x04\x20\x01(\tR\x04de\
sc\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\t.ViewTypeR\x08viewType\x12\
\x18\n\x07version\x18\x06\x20\x01(\x03R\x07version\x12-\n\nbelongings\
\x18\x07\x20\x01(\x0b2\r.RepeatedViewR\nbelongings\x12#\n\rmodified_time\
\x18\x08\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\n\x0bcreate_time\x18\t\
\x20\x01(\x03R\ncreateTime\"+\n\x0cRepeatedView\x12\x1b\n\x05items\x18\
\x01\x20\x03(\x0b2\x05.ViewR\x05items*\x1e\n\x08ViewType\x12\t\n\x05Blan\
k\x10\0\x12\x07\n\x03Doc\x10\x01J\xd1\n\n\x06\x12\x04\0\0!\x01\n\x08\n\
\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\x04\x02\0\x08\x01\n\n\n\x03\
\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\x04\x04\0\x02\0\x12\x03\x03\x04\
\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\x04\n\n\x0c\n\x05\x04\0\x02\
\0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\x02\0\x03\x12\x03\x03\x1a\
\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\x14\n\x0c\n\x05\x04\0\x02\
\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\x02\x01\x01\x12\x03\x04\x0b\
\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\x04\x12\x13\n\x0b\n\x04\x04\0\
\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\x04\0\x02\x02\x05\x12\x03\x05\
\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\x03\x05\x0b\x0f\n\x0c\n\x05\x04\
\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\n\x04\x04\0\x08\0\x12\x03\x06\
\x044\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\x06\n\x1a\n\x0b\n\x04\x04\0\
\x02\x03\x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\x02\x03\x05\x12\x03\x06\x1d#\
\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$-\n\x0c\n\x05\x04\0\x02\x03\
\x03\x12\x03\x0601\n\x0b\n\x04\x04\0\x02\x04\x12\x03\x07\x04\x1b\n\x0c\n\
\x05\x04\0\x02\x04\x06\x12\x03\x07\x04\x0c\n\x0c\n\x05\x04\0\x02\x04\x01\
\x12\x03\x07\r\x16\n\x0c\n\x05\x04\0\x02\x04\x03\x12\x03\x07\x19\x1a\n\n\
\n\x02\x04\x01\x12\x04\t\0\x0f\x01\n\n\n\x03\x04\x01\x01\x12\x03\t\x08\
\x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\x1c\n\x0c\n\x05\x04\x01\x02\
\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\x02\0\x01\x12\x03\n\x0b\x17\n\
\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x1a\x1b\n\x0b\n\x04\x04\x01\x02\
\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x01\x02\x01\x05\x12\x03\x0b\x04\
\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\x0b\x0b\x0f\n\x0c\n\x05\x04\
\x01\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\x04\x04\x01\x02\x02\x12\x03\
\x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\x12\x03\x0c\x04\n\n\x0c\n\
\x05\x04\x01\x02\x02\x01\x12\x03\x0c\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x02\
\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x01\x02\x03\x12\x03\r\x04\x19\n\
\x0c\n\x05\x04\x01\x02\x03\x05\x12\x03\r\x04\n\n\x0c\n\x05\x04\x01\x02\
\x03\x01\x12\x03\r\x0b\x14\n\x0c\n\x05\x04\x01\x02\x03\x03\x12\x03\r\x17\
\x18\n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\x04\x1b\n\x0c\n\x05\x04\x01\
\x02\x04\x06\x12\x03\x0e\x04\x0c\n\x0c\n\x05\x04\x01\x02\x04\x01\x12\x03\
\x0e\r\x16\n\x0c\n\x05\x04\x01\x02\x04\x03\x12\x03\x0e\x19\x1a\n\n\n\x02\
\x04\x02\x12\x04\x10\0\x1a\x01\n\n\n\x03\x04\x02\x01\x12\x03\x10\x08\x0c\
\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x11\x04\x12\n\x0c\n\x05\x04\x02\x02\0\
\x05\x12\x03\x11\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\x11\x0b\r\n\
\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x11\x10\x11\n\x0b\n\x04\x04\x02\x02\
\x01\x12\x03\x12\x04\x1c\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\x03\x12\x04\
\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x12\x0b\x17\n\x0c\n\x05\x04\
\x02\x02\x01\x03\x12\x03\x12\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x02\x12\x03\
\x13\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x13\x04\n\n\x0c\n\
\x05\x04\x02\x02\x02\x01\x12\x03\x13\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x02\
\x03\x12\x03\x13\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\x14\x04\x14\
\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\x14\x04\n\n\x0c\n\x05\x04\x02\
\x02\x03\x01\x12\x03\x14\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x03\x03\x12\x03\
\x14\x12\x13\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x15\x04\x1b\n\x0c\n\x05\
\x04\x02\x02\x04\x06\x12\x03\x15\x04\x0c\n\x0c\n\x05\x04\x02\x02\x04\x01\
\x12\x03\x15\r\x16\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\x15\x19\x1a\n\
\x0b\n\x04\x04\x02\x02\x05\x12\x03\x16\x04\x16\n\x0c\n\x05\x04\x02\x02\
\x05\x05\x12\x03\x16\x04\t\n\x0c\n\x05\x04\x02\x02\x05\x01\x12\x03\x16\n\
\x11\n\x0c\n\x05\x04\x02\x02\x05\x03\x12\x03\x16\x14\x15\n\x0b\n\x04\x04\
\x02\x02\x06\x12\x03\x17\x04\x20\n\x0c\n\x05\x04\x02\x02\x06\x06\x12\x03\
\x17\x04\x10\n\x0c\n\x05\x04\x02\x02\x06\x01\x12\x03\x17\x11\x1b\n\x0c\n\
\x05\x04\x02\x02\x06\x03\x12\x03\x17\x1e\x1f\n\x0b\n\x04\x04\x02\x02\x07\
\x12\x03\x18\x04\x1c\n\x0c\n\x05\x04\x02\x02\x07\x05\x12\x03\x18\x04\t\n\
\x0c\n\x05\x04\x02\x02\x07\x01\x12\x03\x18\n\x17\n\x0c\n\x05\x04\x02\x02\
\x07\x03\x12\x03\x18\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x08\x12\x03\x19\x04\
\x1a\n\x0c\n\x05\x04\x02\x02\x08\x05\x12\x03\x19\x04\t\n\x0c\n\x05\x04\
\x02\x02\x08\x01\x12\x03\x19\n\x15\n\x0c\n\x05\x04\x02\x02\x08\x03\x12\
\x03\x19\x18\x19\n\n\n\x02\x04\x03\x12\x04\x1b\0\x1d\x01\n\n\n\x03\x04\
\x03\x01\x12\x03\x1b\x08\x14\n\x0b\n\x04\x04\x03\x02\0\x12\x03\x1c\x04\
\x1c\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x1c\x04\x0c\n\x0c\n\x05\x04\
\x03\x02\0\x06\x12\x03\x1c\r\x11\n\x0c\n\x05\x04\x03\x02\0\x01\x12\x03\
\x1c\x12\x17\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x1c\x1a\x1b\n\n\n\x02\
\x05\0\x12\x04\x1e\0!\x01\n\n\n\x03\x05\0\x01\x12\x03\x1e\x05\r\n\x0b\n\
\x04\x05\0\x02\0\x12\x03\x1f\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\x12\x03\
\x1f\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03\x1f\x0c\r\n\x0b\n\x04\x05\
\0\x02\x01\x12\x03\x20\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\x20\
\x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\x20\n\x0bb\x06proto3\
Type\x12\x1b\n\tview_data\x18\x06\x20\x01(\tR\x08viewData\x12\x17\n\x07v\
iew_id\x18\x07\x20\x01(\tR\x06viewId\"\x97\x02\n\x04View\x12\x0e\n\x02id\
\x18\x01\x20\x01(\tR\x02id\x12\x20\n\x0cbelong_to_id\x18\x02\x20\x01(\tR\
\nbelongToId\x12\x12\n\x04name\x18\x03\x20\x01(\tR\x04name\x12\x12\n\x04\
desc\x18\x04\x20\x01(\tR\x04desc\x12&\n\tview_type\x18\x05\x20\x01(\x0e2\
\t.ViewTypeR\x08viewType\x12\x18\n\x07version\x18\x06\x20\x01(\x03R\x07v\
ersion\x12-\n\nbelongings\x18\x07\x20\x01(\x0b2\r.RepeatedViewR\nbelongi\
ngs\x12#\n\rmodified_time\x18\x08\x20\x01(\x03R\x0cmodifiedTime\x12\x1f\
\n\x0bcreate_time\x18\t\x20\x01(\x03R\ncreateTime\"+\n\x0cRepeatedView\
\x12\x1b\n\x05items\x18\x01\x20\x03(\x0b2\x05.ViewR\x05items*\x1e\n\x08V\
iewType\x12\t\n\x05Blank\x10\0\x12\x07\n\x03Doc\x10\x01J\xbf\x0b\n\x06\
\x12\x04\0\0#\x01\n\x08\n\x01\x0c\x12\x03\0\0\x12\n\n\n\x02\x04\0\x12\
\x04\x02\0\x08\x01\n\n\n\x03\x04\0\x01\x12\x03\x02\x08\x19\n\x0b\n\x04\
\x04\0\x02\0\x12\x03\x03\x04\x1c\n\x0c\n\x05\x04\0\x02\0\x05\x12\x03\x03\
\x04\n\n\x0c\n\x05\x04\0\x02\0\x01\x12\x03\x03\x0b\x17\n\x0c\n\x05\x04\0\
\x02\0\x03\x12\x03\x03\x1a\x1b\n\x0b\n\x04\x04\0\x02\x01\x12\x03\x04\x04\
\x14\n\x0c\n\x05\x04\0\x02\x01\x05\x12\x03\x04\x04\n\n\x0c\n\x05\x04\0\
\x02\x01\x01\x12\x03\x04\x0b\x0f\n\x0c\n\x05\x04\0\x02\x01\x03\x12\x03\
\x04\x12\x13\n\x0b\n\x04\x04\0\x02\x02\x12\x03\x05\x04\x14\n\x0c\n\x05\
\x04\0\x02\x02\x05\x12\x03\x05\x04\n\n\x0c\n\x05\x04\0\x02\x02\x01\x12\
\x03\x05\x0b\x0f\n\x0c\n\x05\x04\0\x02\x02\x03\x12\x03\x05\x12\x13\n\x0b\
\n\x04\x04\0\x08\0\x12\x03\x06\x044\n\x0c\n\x05\x04\0\x08\0\x01\x12\x03\
\x06\n\x1a\n\x0b\n\x04\x04\0\x02\x03\x12\x03\x06\x1d2\n\x0c\n\x05\x04\0\
\x02\x03\x05\x12\x03\x06\x1d#\n\x0c\n\x05\x04\0\x02\x03\x01\x12\x03\x06$\
-\n\x0c\n\x05\x04\0\x02\x03\x03\x12\x03\x0601\n\x0b\n\x04\x04\0\x02\x04\
\x12\x03\x07\x04\x1b\n\x0c\n\x05\x04\0\x02\x04\x06\x12\x03\x07\x04\x0c\n\
\x0c\n\x05\x04\0\x02\x04\x01\x12\x03\x07\r\x16\n\x0c\n\x05\x04\0\x02\x04\
\x03\x12\x03\x07\x19\x1a\n\n\n\x02\x04\x01\x12\x04\t\0\x11\x01\n\n\n\x03\
\x04\x01\x01\x12\x03\t\x08\x18\n\x0b\n\x04\x04\x01\x02\0\x12\x03\n\x04\
\x1c\n\x0c\n\x05\x04\x01\x02\0\x05\x12\x03\n\x04\n\n\x0c\n\x05\x04\x01\
\x02\0\x01\x12\x03\n\x0b\x17\n\x0c\n\x05\x04\x01\x02\0\x03\x12\x03\n\x1a\
\x1b\n\x0b\n\x04\x04\x01\x02\x01\x12\x03\x0b\x04\x14\n\x0c\n\x05\x04\x01\
\x02\x01\x05\x12\x03\x0b\x04\n\n\x0c\n\x05\x04\x01\x02\x01\x01\x12\x03\
\x0b\x0b\x0f\n\x0c\n\x05\x04\x01\x02\x01\x03\x12\x03\x0b\x12\x13\n\x0b\n\
\x04\x04\x01\x02\x02\x12\x03\x0c\x04\x14\n\x0c\n\x05\x04\x01\x02\x02\x05\
\x12\x03\x0c\x04\n\n\x0c\n\x05\x04\x01\x02\x02\x01\x12\x03\x0c\x0b\x0f\n\
\x0c\n\x05\x04\x01\x02\x02\x03\x12\x03\x0c\x12\x13\n\x0b\n\x04\x04\x01\
\x02\x03\x12\x03\r\x04\x19\n\x0c\n\x05\x04\x01\x02\x03\x05\x12\x03\r\x04\
\n\n\x0c\n\x05\x04\x01\x02\x03\x01\x12\x03\r\x0b\x14\n\x0c\n\x05\x04\x01\
\x02\x03\x03\x12\x03\r\x17\x18\n\x0b\n\x04\x04\x01\x02\x04\x12\x03\x0e\
\x04\x1b\n\x0c\n\x05\x04\x01\x02\x04\x06\x12\x03\x0e\x04\x0c\n\x0c\n\x05\
\x04\x01\x02\x04\x01\x12\x03\x0e\r\x16\n\x0c\n\x05\x04\x01\x02\x04\x03\
\x12\x03\x0e\x19\x1a\n\x0b\n\x04\x04\x01\x02\x05\x12\x03\x0f\x04\x19\n\
\x0c\n\x05\x04\x01\x02\x05\x05\x12\x03\x0f\x04\n\n\x0c\n\x05\x04\x01\x02\
\x05\x01\x12\x03\x0f\x0b\x14\n\x0c\n\x05\x04\x01\x02\x05\x03\x12\x03\x0f\
\x17\x18\n\x0b\n\x04\x04\x01\x02\x06\x12\x03\x10\x04\x17\n\x0c\n\x05\x04\
\x01\x02\x06\x05\x12\x03\x10\x04\n\n\x0c\n\x05\x04\x01\x02\x06\x01\x12\
\x03\x10\x0b\x12\n\x0c\n\x05\x04\x01\x02\x06\x03\x12\x03\x10\x15\x16\n\n\
\n\x02\x04\x02\x12\x04\x12\0\x1c\x01\n\n\n\x03\x04\x02\x01\x12\x03\x12\
\x08\x0c\n\x0b\n\x04\x04\x02\x02\0\x12\x03\x13\x04\x12\n\x0c\n\x05\x04\
\x02\x02\0\x05\x12\x03\x13\x04\n\n\x0c\n\x05\x04\x02\x02\0\x01\x12\x03\
\x13\x0b\r\n\x0c\n\x05\x04\x02\x02\0\x03\x12\x03\x13\x10\x11\n\x0b\n\x04\
\x04\x02\x02\x01\x12\x03\x14\x04\x1c\n\x0c\n\x05\x04\x02\x02\x01\x05\x12\
\x03\x14\x04\n\n\x0c\n\x05\x04\x02\x02\x01\x01\x12\x03\x14\x0b\x17\n\x0c\
\n\x05\x04\x02\x02\x01\x03\x12\x03\x14\x1a\x1b\n\x0b\n\x04\x04\x02\x02\
\x02\x12\x03\x15\x04\x14\n\x0c\n\x05\x04\x02\x02\x02\x05\x12\x03\x15\x04\
\n\n\x0c\n\x05\x04\x02\x02\x02\x01\x12\x03\x15\x0b\x0f\n\x0c\n\x05\x04\
\x02\x02\x02\x03\x12\x03\x15\x12\x13\n\x0b\n\x04\x04\x02\x02\x03\x12\x03\
\x16\x04\x14\n\x0c\n\x05\x04\x02\x02\x03\x05\x12\x03\x16\x04\n\n\x0c\n\
\x05\x04\x02\x02\x03\x01\x12\x03\x16\x0b\x0f\n\x0c\n\x05\x04\x02\x02\x03\
\x03\x12\x03\x16\x12\x13\n\x0b\n\x04\x04\x02\x02\x04\x12\x03\x17\x04\x1b\
\n\x0c\n\x05\x04\x02\x02\x04\x06\x12\x03\x17\x04\x0c\n\x0c\n\x05\x04\x02\
\x02\x04\x01\x12\x03\x17\r\x16\n\x0c\n\x05\x04\x02\x02\x04\x03\x12\x03\
\x17\x19\x1a\n\x0b\n\x04\x04\x02\x02\x05\x12\x03\x18\x04\x16\n\x0c\n\x05\
\x04\x02\x02\x05\x05\x12\x03\x18\x04\t\n\x0c\n\x05\x04\x02\x02\x05\x01\
\x12\x03\x18\n\x11\n\x0c\n\x05\x04\x02\x02\x05\x03\x12\x03\x18\x14\x15\n\
\x0b\n\x04\x04\x02\x02\x06\x12\x03\x19\x04\x20\n\x0c\n\x05\x04\x02\x02\
\x06\x06\x12\x03\x19\x04\x10\n\x0c\n\x05\x04\x02\x02\x06\x01\x12\x03\x19\
\x11\x1b\n\x0c\n\x05\x04\x02\x02\x06\x03\x12\x03\x19\x1e\x1f\n\x0b\n\x04\
\x04\x02\x02\x07\x12\x03\x1a\x04\x1c\n\x0c\n\x05\x04\x02\x02\x07\x05\x12\
\x03\x1a\x04\t\n\x0c\n\x05\x04\x02\x02\x07\x01\x12\x03\x1a\n\x17\n\x0c\n\
\x05\x04\x02\x02\x07\x03\x12\x03\x1a\x1a\x1b\n\x0b\n\x04\x04\x02\x02\x08\
\x12\x03\x1b\x04\x1a\n\x0c\n\x05\x04\x02\x02\x08\x05\x12\x03\x1b\x04\t\n\
\x0c\n\x05\x04\x02\x02\x08\x01\x12\x03\x1b\n\x15\n\x0c\n\x05\x04\x02\x02\
\x08\x03\x12\x03\x1b\x18\x19\n\n\n\x02\x04\x03\x12\x04\x1d\0\x1f\x01\n\n\
\n\x03\x04\x03\x01\x12\x03\x1d\x08\x14\n\x0b\n\x04\x04\x03\x02\0\x12\x03\
\x1e\x04\x1c\n\x0c\n\x05\x04\x03\x02\0\x04\x12\x03\x1e\x04\x0c\n\x0c\n\
\x05\x04\x03\x02\0\x06\x12\x03\x1e\r\x11\n\x0c\n\x05\x04\x03\x02\0\x01\
\x12\x03\x1e\x12\x17\n\x0c\n\x05\x04\x03\x02\0\x03\x12\x03\x1e\x1a\x1b\n\
\n\n\x02\x05\0\x12\x04\x20\0#\x01\n\n\n\x03\x05\0\x01\x12\x03\x20\x05\r\
\n\x0b\n\x04\x05\0\x02\0\x12\x03!\x04\x0e\n\x0c\n\x05\x05\0\x02\0\x01\
\x12\x03!\x04\t\n\x0c\n\x05\x05\0\x02\0\x02\x12\x03!\x0c\r\n\x0b\n\x04\
\x05\0\x02\x01\x12\x03\"\x04\x0c\n\x0c\n\x05\x05\0\x02\x01\x01\x12\x03\"\
\x04\x07\n\x0c\n\x05\x05\0\x02\x01\x02\x12\x03\"\n\x0bb\x06proto3\
";
static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

View File

@ -13,6 +13,8 @@ message CreateViewParams {
string desc = 3;
string thumbnail = 4;
ViewType view_type = 5;
string view_data = 6;
string view_id = 7;
}
message View {
string id = 1;

View File

@ -2,7 +2,7 @@ pub mod future;
pub mod retry;
#[allow(dead_code)]
pub fn uuid() -> String { uuid::Uuid::new_v4().to_string() }
pub fn uuid_string() -> String { uuid::Uuid::new_v4().to_string() }
#[allow(dead_code)]
pub fn timestamp() -> i64 { chrono::Utc::now().timestamp() }